<template>
  <QuickNav />
  <TopMessage />

  <!-- CONTENT -->
  <section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
    <!-- Breadcrumb -->
    <div id="ViewBreadcrumb">
      <div>
        <h1 class="text-center text-lg font-light relative px-7">
          <!-- Back -->
          <button
            class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
            @click="$router.back()"
          >
            <font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
          </button>
          Project Idea
        </h1>
        <h2 class="text-center text-xl font-semibold">
          {{ name }}
          <button
            v-if="activeDid === issuer || activeDid === agentDid"
            title="Edit"
            data-testId="editClaimButton"
            @click="onEditClick()"
          >
            <font-awesome icon="pen" class="text-sm text-blue-500 ml-2 mb-1" />
          </button>
          <button title="Copy Link to Project" @click="onCopyLinkClick()">
            <font-awesome
              icon="link"
              class="text-sm text-slate-500 ml-2 mb-1"
            />
          </button>
        </h2>
      </div>
    </div>

    <!-- Project Details -->
    <div class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mt-4">
      <div>
        <div class="pb-4 flex gap-4">
          <div class="pt-1">
            <ProjectIcon
              :entity-id="projectId"
              :icon-size="64"
              :image-url="imageUrl"
              :link-to-full="true"
              class="block border border-slate-300 rounded-md max-h-16 max-w-16"
            />
          </div>

          <div class="overflow-hidden">
            <div class="text-sm mb-3">
              <div class="truncate">
                <font-awesome
                  icon="user"
                  class="fa-fw text-slate-400"
                ></font-awesome>
                <span class="truncate inline-block max-w-[calc(100%-2rem)]">
                  {{ issuerInfoObject?.displayName }}
                </span>

                <span
                  v-if="!serverUtil.isHiddenDid(issuer)"
                  class="inline-flex items-center"
                >
                  <router-link
                    :to="{
                      path: '/did/' + encodeURIComponent(issuer),
                    }"
                    class="text-blue-500 ml-1"
                    title="See more about this person"
                  >
                    <font-awesome
                      icon="arrow-up-right-from-square"
                      class="fa-fw"
                    />
                  </router-link>
                </span>
                <span v-if="serverUtil.isHiddenDid(issuer)" class="ml-1">
                  <font-awesome
                    icon="info-circle"
                    class="fa-fw text-blue-500 cursor-pointer"
                    @click="openHiddenDidDialog()"
                  />
                </span>
              </div>
              <div v-if="startTime">
                <font-awesome
                  icon="calendar"
                  class="fa-fw text-slate-400"
                ></font-awesome>
                Starts {{ startTime }}
              </div>
              <div v-if="endTime">
                <font-awesome
                  icon="calendar"
                  class="fa-fw text-slate-400"
                ></font-awesome>
                Ends {{ endTime }}
              </div>
              <div v-if="latitude || longitude">
                <font-awesome
                  icon="location-dot"
                  class="fa-fw text-slate-400"
                ></font-awesome>
                <a
                  :href="getOpenStreetMapUrl()"
                  target="_blank"
                  class="underline text-blue-500"
                  >Map View
                  <font-awesome
                    icon="arrow-up-right-from-square"
                    class="fa-fw text-blue-500"
                  />
                </a>
              </div>
              <div v-if="url">
                <font-awesome
                  icon="globe"
                  class="fa-fw text-slate-400"
                ></font-awesome>
                <a
                  :href="ensureScheme(url)"
                  target="_blank"
                  class="underline text-blue-500"
                >
                  {{ domainForWebsite(url) }}
                  <font-awesome
                    icon="arrow-up-right-from-square"
                    class="fa-fw"
                  />
                </a>
              </div>
            </div>
          </div>
        </div>

        <div class="text-sm text-slate-500">
          <div v-if="!expanded">
            {{ truncatedDesc }}
            <a
              v-if="description.length >= truncateLength"
              class="uppercase text-xs font-semibold text-slate-700"
              @click="expandText"
              >... Read More</a
            >
          </div>
          <div v-else>
            {{ description }}
            <a
              class="uppercase text-xs font-semibold text-slate-700"
              @click="collapseText"
              >- Read Less</a
            >
          </div>
        </div>

        <a class="cursor-pointer" @click="onClickLoadClaim(projectId)">
          <font-awesome icon="file-lines" class="pl-2 pt-1 text-blue-500" />
        </a>
      </div>
    </div>

    <div class="grid items-start grid-cols-1 sm:grid-cols-2 gap-4 mt-4">
      <div>
        <div
          v-if="fulfillersToThis.length > 0"
          class="bg-slate-100 px-4 py-3 rounded-md"
        >
          <h3 class="text-sm uppercase font-semibold mt-3">
            Projects That Contribute To This
          </h3>
          <!-- 
          centering because long, wrapped project names didn't left align with blank 
          or "text-left" 
          -->
          <div class="text-center">
            <div v-for="plan in fulfillersToThis" :key="plan.handleId">
              <button
                class="text-blue-500"
                @click="onClickLoadProject(plan.handleId)"
              >
                {{ plan.name || unnamedProject }}
              </button>
            </div>
            <div v-if="fulfillersToHitLimit" class="text-center">
              <button @click="loadPlanFulfillersTo()">Load More</button>
            </div>
          </div>
        </div>
      </div>

      <div>
        <div v-if="fulfilledByThis" class="bg-slate-100 px-4 py-3 rounded-md">
          <h3 class="text-sm uppercase font-semibold mb-3">
            Projects Getting Contributions From This
          </h3>
          <!-- 
          centering because long, wrapped project names didn't left align with blank 
          or "text-left" 
          -->
          <div class="text-center">
            <button
              class="text-blue-500"
              @click="onClickLoadProject(fulfilledByThis.handleId)"
            >
              {{ fulfilledByThis.name || unnamedProject }}
            </button>
          </div>
        </div>
      </div>
    </div>

    <GiftedDialog
      ref="giveDialogToThis"
      :giver-entity-type="'person'"
      :recipient-entity-type="'project'"
      :to-project-id="projectId"
      :is-from-project-view="true"
    />

    <!-- Offers & Gifts to & from this -->
    <div class="grid items-start grid-cols-1 sm:grid-cols-3 gap-4 mt-4">
      <!-- First, offers on the left-->
      <div class="bg-slate-100 px-4 py-3 rounded-md">
        <div v-if="activeDid && isRegistered" class="mb-4">
          <div class="text-center">
            <button
              data-testId="offerButton"
              class="block w-full 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-3 py-1.5 text-sm leading-tight rounded-md"
              @click="openOfferDialog()"
            >
              Offer to this (maybe with conditions)...
            </button>
          </div>
        </div>
        <OfferDialog
          ref="customOfferDialog"
          :project-id="projectId"
          :project-name="name"
        />

        <h3 class="text-lg font-bold leading-tight mb-3">
          Offered To This Idea
        </h3>

        <div v-if="offersToThis.length === 0" class="text-sm">
          (None yet.<span v-if="activeDid && isRegistered">
            Wanna
            <span
              class="cursor-pointer text-blue-500"
              @click="openOfferDialog()"
              >offer something&hellip; especially if others join you</span
            >?</span
          >)
        </div>

        <ul v-else class="text-sm border-t border-slate-300">
          <li
            v-for="offer in offersToThis"
            :key="offer.jwtId"
            class="py-1.5 border-b border-slate-300"
          >
            <div class="flex justify-between gap-4">
              <span>
                <font-awesome
                  icon="user"
                  class="fa-fw text-slate-400"
                ></font-awesome>
                {{
                  serverUtil.didInfo(
                    offer.offeredByDid,
                    activeDid,
                    allMyDids,
                    allContacts,
                  )
                }}
              </span>
              <span v-if="offer.amount" class="whitespace-nowrap">
                <font-awesome
                  :icon="libsUtil.iconForUnitCode(offer.unit)"
                  class="fa-fw text-slate-400"
                />{{ offer.amount }}
              </span>
            </div>
            <div v-if="offer.objectDescription" class="text-slate-500">
              <font-awesome icon="comment" class="fa-fw text-slate-400" />
              {{ offer.objectDescription }}
            </div>
            <div class="flex justify-between">
              <a
                class="cursor-pointer"
                @click="onClickLoadClaim(offer.jwtId as string)"
              >
                <font-awesome
                  icon="file-lines"
                  class="pl-2 pt-1 text-blue-500"
                />
              </a>
              <a
                v-if="checkIsFulfillable(offer)"
                @click="onClickFulfillGiveToOffer(offer)"
              >
                <font-awesome
                  icon="hand-holding-heart"
                  class="text-blue-500 cursor-pointer"
                />
              </a>
            </div>
          </li>
        </ul>
        <div v-if="offersHitLimit" class="text-center text-blue-500">
          <button @click="loadOffers()">Load More</button>
        </div>
      </div>

      <!-- Now, gives TO this project in the middle -->
      <!-- (similar to "FROM" gift display below) -->
      <div class="bg-slate-100 px-4 py-3 rounded-md" data-testId="gives-to">
        <div v-if="activeDid && isRegistered" class="mb-4">
          <div class="text-center">
            <button
              class="block w-full 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-3 py-1.5 text-sm leading-tight rounded-md"
              @click="openGiftDialogToProject()"
            >
              Given To This...
            </button>
          </div>
        </div>

        <h3 class="text-lg font-bold leading-tight mb-3">
          Given To This Project
        </h3>

        <div v-if="givesToThis.length === 0" class="text-sm">
          (None yet. If you've seen something, say something by clicking a
          contact above.)
        </div>

        <div v-else class="mt-1 text-sm">
          <!-- Totals section -->
          <div class="mt-1 flex items-center min-h-[1.5rem]">
            <div v-if="loadingTotals" class="flex-1">
              <font-awesome
                icon="spinner"
                class="fa-spin-pulse text-blue-500"
              />
            </div>
            <div v-else-if="givesTotalsByUnit.length > 0" class="flex-1">
              <span class="font-semibold mr-2 shrink-0">Totals</span>
              <span class="whitespace-nowrap overflow-hidden text-ellipsis">
                <a
                  class="cursor-pointer text-blue-500"
                  @click="totalsExpanded = !totalsExpanded"
                >
                  <!-- just show the hours, or alternatively whatever is first -->
                  <span v-if="givenTotalHours() > 0">
                    {{ libsUtil.formattedAmount(givenTotalHours(), "HUR") }}
                  </span>
                  <span v-else>
                    {{
                      libsUtil.formattedAmount(
                        givesTotalsByUnit[0].amount,
                        givesTotalsByUnit[0].unit,
                      )
                    }}
                  </span>
                  <span v-if="givesTotalsByUnit.length > 1">...</span>
                  <span>
                    <font-awesome
                      :icon="totalsExpanded ? 'chevron-up' : 'chevron-right'"
                      class="fa-fw text-xs ml-1"
                    />
                  </span>
                </a>
                <!-- show the full list when expanded -->
                <div v-if="totalsExpanded">
                  <div
                    v-for="total in givesTotalsByUnit"
                    :key="total.unit"
                    class="ml-2"
                  >
                    <font-awesome
                      :icon="libsUtil.iconForUnitCode(total.unit)"
                      class="fa-fw text-slate-400 mr-1"
                    />
                    {{ libsUtil.formattedAmount(total.amount, total.unit) }}
                  </div>
                </div>
              </span>
            </div>
            <div v-else>
              <span class="font-semibold mr-2 shrink-0">
                {{ givesToThis.length }}{{ givesHitLimit ? "+" : "" }} record{{
                  givesToThis.length === 1 ? "" : "s"
                }}
              </span>
            </div>
          </div>

          <!-- List of gives -->
          <ul class="mt-2 text-sm border-t border-slate-300">
            <li
              v-for="give in givesToThis"
              :key="give.jwtId"
              class="py-1.5 border-b border-slate-300"
            >
              <div class="flex justify-between gap-4">
                <span>
                  <font-awesome icon="user" class="fa-fw text-slate-400" />
                  {{
                    serverUtil.didInfo(
                      give.agentDid,
                      activeDid,
                      allMyDids,
                      allContacts,
                    )
                  }}
                </span>
                <span v-if="give.amount" class="whitespace-nowrap">
                  <font-awesome
                    :icon="libsUtil.iconForUnitCode(give.unit)"
                    class="fa-fw text-slate-400"
                  />{{ give.amount }}
                </span>
              </div>
              <div class="text-slate-500">
                <font-awesome icon="calendar" class="fa-fw text-slate-400" />
                {{ give.issuedAt?.substring(0, 10) }}
              </div>
              <div v-if="give.description" class="text-slate-500">
                <font-awesome icon="comment" class="fa-fw text-slate-400" />
                {{ give.description }}
              </div>
              <div class="flex justify-between">
                <a @click="onClickLoadClaim(give.jwtId)">
                  <font-awesome
                    icon="file-lines"
                    class="text-blue-500 cursor-pointer"
                  />
                </a>

                <a
                  v-if="
                    checkIsConfirmable(give) &&
                    !recentlyCheckedAndUnconfirmableJwts.includes(give.jwtId)
                  "
                  @click="deepCheckConfirmable(give)"
                >
                  <font-awesome
                    icon="circle-check"
                    class="text-blue-500 cursor-pointer"
                  />
                </a>
                <a v-else-if="checkingConfirmationForJwtId === give.jwtId">
                  <font-awesome icon="spinner" class="fa-spin-pulse" />
                </a>
                <a v-else @click="shallowNotifyWhyCannotConfirm(give)">
                  <font-awesome
                    icon="circle-check"
                    class="text-slate-500 cursor-pointer"
                  />
                </a>
              </div>
              <div v-if="give.fullClaim.image" class="flex justify-center">
                <a :href="give.fullClaim.image" target="_blank">
                  <img
                    :src="give.fullClaim.image"
                    class="h-24 mt-2 rounded-xl"
                  />
                </a>
              </div>
            </li>
          </ul>
        </div>
        <div v-if="givesHitLimit" class="text-center text-blue-500">
          <button @click="loadGives()">Load More</button>
        </div>
      </div>

      <!-- Finally, gives FROM this project on the right -->
      <!-- (similar to "TO" gift display above) -->
      <div class="bg-slate-100 px-4 py-3 rounded-md" data-testId="gives-from">
        <div v-if="activeDid && isRegistered" class="mb-4">
          <div class="text-center">
            <button
              class="block w-full 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-3 py-1.5 text-sm leading-tight rounded-md"
              @click="openGiftDialogFromProject()"
            >
              Given By This...
            </button>
          </div>
        </div>
        <GiftedDialog
          ref="giveDialogFromThis"
          :giver-entity-type="'project'"
          :recipient-entity-type="'person'"
          :from-project-id="projectId"
          :is-from-project-view="true"
        />

        <h3 class="text-lg font-bold leading-tight mb-3">
          Benefitted From This Project
        </h3>

        <div v-if="givesProvidedByThis.length === 0" class="text-sm">
          (None yet.)
        </div>

        <ul v-else class="text-sm border-t border-slate-300">
          <li
            v-for="give in givesProvidedByThis"
            :key="give.jwtId"
            class="py-1.5 border-b border-slate-300"
          >
            <div class="flex justify-between gap-4">
              <span>
                {{
                  serverUtil.didInfo(
                    give.recipientDid,
                    activeDid,
                    allMyDids,
                    allContacts,
                  )
                }}
              </span>
              <span v-if="give.amount" class="whitespace-nowrap">
                <font-awesome
                  :icon="libsUtil.iconForUnitCode(give.unit)"
                  class="fa-fw text-slate-400"
                />{{ give.amount }}
              </span>
            </div>
            <div class="text-slate-500">
              <font-awesome icon="calendar" class="fa-fw text-slate-400" />
              {{ give.issuedAt?.substring(0, 10) }}
            </div>
            <div v-if="give.description" class="text-slate-500">
              <font-awesome icon="comment" class="fa-fw text-slate-400" />
              {{ give.description }}
            </div>
            <div class="flex justify-between">
              <a @click="onClickLoadClaim(give.jwtId)">
                <font-awesome
                  icon="file-lines"
                  class="text-blue-500 cursor-pointer"
                />
              </a>

              <a
                v-if="
                  checkIsConfirmable(give) &&
                  !recentlyCheckedAndUnconfirmableJwts.includes(give.jwtId)
                "
                @click="deepCheckConfirmable(give)"
              >
                <font-awesome
                  icon="circle-check"
                  class="text-blue-500 cursor-pointer"
                />
              </a>
              <a v-else-if="checkingConfirmationForJwtId === give.jwtId">
                <font-awesome icon="spinner" class="fa-spin-pulse" />
              </a>
              <a v-else @click="shallowNotifyWhyCannotConfirm(give)">
                <font-awesome
                  icon="circle-check"
                  class="text-slate-500 cursor-pointer"
                />
              </a>
            </div>
            <div v-if="give.fullClaim.image" class="flex justify-center">
              <a :href="give.fullClaim.image" target="_blank">
                <img :src="give.fullClaim.image" class="h-24 mt-2 rounded-xl" />
              </a>
            </div>
          </li>
        </ul>
        <div v-if="givesProvidedByHitLimit" class="text-center">
          <button @click="loadGivesProvidedBy()">Load More</button>
        </div>
      </div>
    </div>
  </section>

  <HiddenDidDialog ref="hiddenDidDialog" />
</template>

<script lang="ts">
import { AxiosError } from "axios";
import { Component, Vue } from "vue-facing-decorator";
import { Router } from "vue-router";
import {
  GenericVerifiableCredential,
  GenericCredWrapper,
  GiveSummaryRecord,
  GiveActionClaim,
  OfferSummaryRecord,
  OfferClaim,
  PlanSummaryRecord,
} from "../interfaces";
import GiftedDialog from "../components/GiftedDialog.vue";
import OfferDialog from "../components/OfferDialog.vue";
import TopMessage from "../components/TopMessage.vue";
import QuickNav from "../components/QuickNav.vue";
import EntityIcon from "../components/EntityIcon.vue";
import ProjectIcon from "../components/ProjectIcon.vue";
import { NotificationIface } from "../constants/app";
// Removed legacy logging import - migrated to PlatformServiceMixin
import { Contact } from "../db/tables/contacts";
import * as libsUtil from "../libs/util";
import * as serverUtil from "../libs/endorserServer";
import { retrieveAccountDids } from "../libs/util";
import HiddenDidDialog from "../components/HiddenDidDialog.vue";
import { logger } from "../utils/logger";
import { copyToClipboard } from "../services/ClipboardService";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import { NOTIFY_CONFIRM_CLAIM } from "@/constants/notifications";
import { APP_SERVER } from "@/constants/app";
import { UNNAMED_PROJECT } from "@/constants/entities";
/**
 * Project View Component
 * @author Matthew Raymer
 *
 * This component displays and manages detailed project information. It handles:
 * - Project loading and display from URL-encoded project handles
 * - Project metadata (name, description, dates, location)
 * - Issuer information and verification
 * - Project contributions and fulfillments
 * - Offers and gifts tracking
 * - Contact interactions
 *
 * Data Flow:
 * 1. Component loads with project ID from route
 * 2. Fetches project data, contacts, and account settings
 * 3. Loads related data (offers, gifts, fulfillments)
 * 4. Updates UI with paginated results
 *
 * Security Features:
 * - DID visibility controls
 * - JWT validation for imports
 * - Permission checks for actions
 *
 * State Management:
 * - Maintains separate loading states for different data types
 * - Handles pagination limits
 * - Tracks confirmation states
 *
 * @see GiftedDialog for gift creation
 * @see OfferDialog for offer creation
 * @see HiddenDidDialog for DID privacy explanations
 */
@Component({
  components: {
    EntityIcon,
    GiftedDialog,
    HiddenDidDialog,
    OfferDialog,
    ProjectIcon,
    QuickNav,
    TopMessage,
  },
  mixins: [PlatformServiceMixin],
})
export default class ProjectViewView extends Vue {
  /** Notification function injected by Vue */
  $notify!: (notification: NotificationIface, timeout?: number) => void;
  /** Router instance for navigation */
  $router!: Router;

  /** Notification helpers instance */
  notify!: ReturnType<typeof createNotifyHelpers>;

  /**
   * Get the unnamed project constant
   */
  get unnamedProject(): string {
    return UNNAMED_PROJECT;
  }

  // Account and Settings State
  /** Currently active DID */
  activeDid = "";
  /** Project agent DID */
  agentDid = "";
  /** DIDs that can see the agent DID */
  agentDidVisibleToDids: Array<string> = [];
  /** All DIDs associated with current account */
  allMyDids: Array<string> = [];
  /** All known contacts */
  allContacts: Array<Contact> = [];
  /** API server endpoint */
  apiServer = "";
  /** Registration status of current user */
  isRegistered = false;

  // Project Data
  /** Project description */
  description = "";
  /** Project end time */
  endTime = "";
  /** Text expansion state */
  expanded = false;
  /** Project fulfilled by this project */
  fulfilledByThis: PlanSummaryRecord | null = null;
  /** Projects fulfilling this project */
  fulfillersToThis: Array<PlanSummaryRecord> = [];
  /** Flag for fulfiller pagination */
  fulfillersToHitLimit = false;
  /** Gifts to this project */
  givesToThis: Array<GiveSummaryRecord> = [];
  givesHitLimit = false;
  givesProvidedByThis: Array<GiveSummaryRecord> = [];
  givesProvidedByHitLimit = false;
  givesTotalsByUnit: Array<{ unit: string; amount: number }> = [];
  imageUrl = "";
  /** Project issuer DID */
  issuer = "";
  /** Cached issuer information */
  issuerInfoObject: {
    known: boolean;
    displayName: string;
    profileImageUrl?: string;
  } | null = null;
  /** DIDs that can see issuer information */
  issuerVisibleToDids: Array<string> = [];
  /** Project location data */
  latitude = 0;
  loadingTotals = false;
  longitude = 0;
  /** Project name */
  name = "";
  /** Project ID (handle) */
  projectId = "";
  /** Project start time */
  startTime = "";
  /** Project URL */
  url = "";

  // Interaction Data
  /** Gifts to this project */
  offersToThis: Array<OfferSummaryRecord> = [];
  /** Flag for offers pagination */
  offersHitLimit = false;

  // UI State
  /** JWT being checked for confirmation */
  checkingConfirmationForJwtId = "";
  /** Recently checked unconfirmable JWTs */
  recentlyCheckedAndUnconfirmableJwts: string[] = [];

  totalsExpanded = false;
  truncatedDesc = "";
  /** Truncation length */
  truncateLength = 40;

  // Utility References
  libsUtil = libsUtil;
  serverUtil = serverUtil;
  /** Production share domain for deep links */
  APP_SERVER = APP_SERVER;

  /**
   * Component lifecycle hook that initializes the project view
   *
   * Workflow:
   * 1. Loads account settings and contacts
   * 2. Retrieves all account DIDs
   * 3. Extracts project ID from URL
   * 4. Initializes project data loading
   *
   * @throws Logs errors but continues loading
   * @emits Notification on profile loading errors
   */
  async created() {
    this.notify = createNotifyHelpers(this.$notify);

    const settings = await this.$accountSettings();

    // Get activeDid from active_identity table (single source of truth)
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const activeIdentity = await (this as any).$getActiveIdentity();
    this.activeDid = activeIdentity.activeDid || "";

    this.apiServer = settings.apiServer || "";
    this.allContacts = await this.$getAllContacts();
    this.isRegistered = !!settings.isRegistered;

    try {
      this.allMyDids = await retrieveAccountDids();
    } catch (error) {
      // continue because we want to see claims, even anonymously
      this.$logAndConsole(
        "Error retrieving all account DIDs on home page:" + error,
        true,
      );
      this.notify.error(
        "See the Help page to fix problems with your personal data.",
        TIMEOUTS.LONG,
      );
    }

    const pathParam = window.location.pathname.substring("/project/".length);
    if (pathParam) {
      this.projectId = decodeURIComponent(pathParam);
    }
    this.loadProject(this.projectId, this.activeDid);
    this.loadTotals();
  }

  /**
   * Navigates to project edit view with current project ID
   */
  onEditClick(): void {
    this.$router.push({
      name: "new-edit-project",
      query: { projectId: this.projectId },
    });
  }

  async onCopyLinkClick() {
    const shortestProjectId = this.projectId.startsWith(
      serverUtil.ENDORSER_CH_HANDLE_PREFIX,
    )
      ? this.projectId.substring(serverUtil.ENDORSER_CH_HANDLE_PREFIX.length)
      : this.projectId;
    // Use production URL for sharing to avoid localhost issues in development
    const deepLink = `${APP_SERVER}/deep-link/project/${shortestProjectId}`;
    try {
      await copyToClipboard(deepLink);
      this.notify.copied("link to this project", TIMEOUTS.SHORT);
    } catch (error) {
      this.$logAndConsole(`Error copying project link: ${error}`, true);
      this.notify.error("Failed to copy project link.");
    }
  }

  // Isn't there a better way to make this available to the template?
  expandText() {
    this.expanded = true;
  }

  collapseText() {
    this.expanded = false;
  }

  async loadProject(projectId: string, userDid: string) {
    this.projectId = projectId;

    const url =
      this.apiServer + "/api/claim/byHandle/" + encodeURIComponent(projectId);
    const headers = await serverUtil.getHeaders(userDid);

    try {
      const resp = await this.axios.get(url, { headers });
      if (resp.status === 200) {
        const startTime = resp.data.claim?.startTime;
        if (startTime != null) {
          const startDateTime = new Date(startTime);
          this.startTime =
            startDateTime.toLocaleDateString() +
            " " +
            startDateTime.toLocaleTimeString();
        }
        const endTime = resp.data.claim?.endTime;
        if (endTime != null) {
          const endDateTime = new Date(endTime);
          this.endTime =
            endDateTime.toLocaleDateString() +
            " " +
            endDateTime.toLocaleTimeString();
        }
        this.agentDid = resp.data.claim?.agent?.identifier;
        this.agentDidVisibleToDids =
          resp.data.claim?.agent?.identifierVisibleToDids || [];
        this.imageUrl = resp.data.claim?.image;
        this.issuer = resp.data.issuer;
        this.issuerInfoObject = serverUtil.didInfoObject(
          this.issuer,
          this.activeDid,
          this.allMyDids,
          this.allContacts,
        );
        this.issuerVisibleToDids = resp.data.issuerVisibleToDids || [];
        this.name = resp.data.claim?.name || "(no name)";
        this.description = resp.data.claim?.description || "(no description)";
        this.truncatedDesc = this.description.slice(0, this.truncateLength);
        this.latitude = resp.data.claim?.location?.geo?.latitude || 0;
        this.longitude = resp.data.claim?.location?.geo?.longitude || 0;
        this.url = resp.data.claim?.url || "";
      } else {
        // actually, axios throws an error on 404 so we probably never get here
        logger.error("Error getting project:", resp);
        this.notify.error(
          "There was a problem getting that project.",
          TIMEOUTS.LONG,
        );
      }
    } catch (error: unknown) {
      logger.error("Error retrieving project:", error);
      this.notify.error(
        "Something went wrong retrieving that project.",
        TIMEOUTS.LONG,
      );
    }

    this.givesToThis = [];
    this.loadGives();

    this.givesProvidedByThis = [];
    this.loadGivesProvidedBy();

    this.offersToThis = [];
    this.loadOffers();

    this.fulfillersToThis = [];
    this.loadPlanFulfillersTo();

    this.fulfilledByThis = null;
    this.loadPlanFulfilledBy();
  }

  /**
   * Loads gifts made to this project
   *
   * Handles pagination and updates component state with results.
   * Uses beforeId for pagination based on last loaded gift.
   *
   * @throws Logs errors and notifies user
   * @emits Notification on loading errors
   */
  async loadGives() {
    const givesUrl =
      this.apiServer +
      "/api/v2/report/givesToPlans?planIds=" +
      encodeURIComponent(JSON.stringify([this.projectId]));
    let postfix = "";
    if (this.givesToThis.length > 0) {
      postfix =
        "&beforeId=" + this.givesToThis[this.givesToThis.length - 1].jwtId;
    }
    const givesInUrl = givesUrl + postfix;

    const headers = await serverUtil.getHeaders(this.activeDid);
    try {
      const resp = await this.axios.get(givesInUrl, { headers });
      if (resp.status === 200 && resp.data.data) {
        this.givesToThis = this.givesToThis.concat(resp.data.data);
        this.givesHitLimit = resp.data.hitLimit;
      } else {
        this.notify.error(
          "Failed to retrieve more gives to this project.",
          TIMEOUTS.LONG,
        );
      }
    } catch (error: unknown) {
      const serverError = error as AxiosError;
      this.notify.error(
        "Something went wrong retrieving more gives to this project.",
        TIMEOUTS.LONG,
      );
      logger.error(
        "Something went wrong retrieving more gives to this project:",
        serverError.message,
      );
    }
  }

  /**
   * Loads gifts provided by this project
   *
   * Similar to loadGives but for outgoing gifts.
   * Maintains separate pagination state.
   *
   * @throws Logs errors and notifies user
   * @emits Notification on loading errors
   */
  async loadGivesProvidedBy() {
    const providedByUrl =
      this.apiServer +
      "/api/v2/report/givesProvidedBy?providerId=" +
      encodeURIComponent(this.projectId);
    let postfix = "";
    if (this.givesProvidedByThis.length > 0) {
      postfix =
        "&beforeId=" +
        this.givesProvidedByThis[this.givesProvidedByThis.length - 1].jwtId;
    }
    const providedByFullUrl = providedByUrl + postfix;

    const headers = await serverUtil.getHeaders(this.activeDid);
    try {
      const resp = await this.axios.get(providedByFullUrl, { headers });
      if (resp.status === 200) {
        this.givesProvidedByThis = this.givesProvidedByThis.concat(
          resp.data.data,
        );
        this.givesProvidedByHitLimit = resp.data.hitLimit;
      } else {
        this.notify.error(
          "Failed to retrieve gives that were provided by this project.",
          TIMEOUTS.LONG,
        );
      }
    } catch (error: unknown) {
      const serverError = error as AxiosError;
      this.notify.error(
        "Something went wrong retrieving gives that were provided by this project.",
        TIMEOUTS.LONG,
      );
      logger.error(
        "Something went wrong retrieving gives that were provided by this project:",
        serverError.message,
      );
    }
  }

  /**
   * Loads offers made to this project
   *
   * Handles pagination and filtering of valid offers.
   * Updates component state with results.
   *
   * @throws Logs errors and notifies user
   * @emits Notification on loading errors
   */
  async loadOffers() {
    const offersUrl =
      this.apiServer +
      "/api/v2/report/offersToPlans?planIds=" +
      encodeURIComponent(JSON.stringify([this.projectId]));
    let postfix = "";
    if (this.offersToThis.length > 0) {
      postfix =
        "&beforeId=" + this.offersToThis[this.offersToThis.length - 1].jwtId;
    }
    const offersInUrl = offersUrl + postfix;

    const headers = await serverUtil.getHeaders(this.activeDid);
    try {
      const resp = await this.axios.get(offersInUrl, { headers });
      if (resp.status === 200 && resp.data.data) {
        this.offersToThis = this.offersToThis.concat(resp.data.data);
        this.offersHitLimit = resp.data.hitLimit;
      } else {
        this.notify.error(
          "Failed to retrieve more offers to this project.",
          TIMEOUTS.LONG,
        );
      }
    } catch (error: unknown) {
      const serverError = error as AxiosError;
      this.notify.error(
        "Something went wrong retrieving more offers to this project.",
        TIMEOUTS.LONG,
      );
      logger.error(
        "Something went wrong retrieving more offers to this project:",
        serverError.message,
      );
    }
  }

  /**
   * Loads projects that fulfill this project
   *
   * Manages pagination state and updates component with results.
   *
   * @throws Logs errors and notifies user
   * @emits Notification on loading errors
   */
  async loadPlanFulfillersTo() {
    const fulfillsUrl =
      this.apiServer +
      "/api/v2/report/planFulfillersToPlan?planHandleId=" +
      encodeURIComponent(this.projectId);
    let postfix = "";
    if (this.fulfillersToThis.length > 0) {
      postfix =
        "&beforeId=" +
        this.fulfillersToThis[this.fulfillersToThis.length - 1].jwtId;
    }
    const fulfillsInUrl = fulfillsUrl + postfix;

    const headers = await serverUtil.getHeaders(this.activeDid);
    try {
      const resp = await this.axios.get(fulfillsInUrl, { headers });
      if (resp.status === 200) {
        this.fulfillersToThis = this.fulfillersToThis.concat(resp.data.data);
        this.fulfillersToHitLimit = resp.data.hitLimit;
      } else {
        this.notify.error(
          "Failed to retrieve more plans that fullfill this project.",
          TIMEOUTS.LONG,
        );
      }
    } catch (error: unknown) {
      const serverError = error as AxiosError;
      this.notify.error(
        "Something went wrong retrieving more plans that fulfull this project.",
        TIMEOUTS.LONG,
      );
      logger.error(
        "Something went wrong retrieving more plans that fulfill this project:",
        serverError.message,
      );
    }
  }

  /**
   * Loads project that this project fulfills
   *
   * Updates fulfilledByThis state with result.
   *
   * @throws Logs errors and notifies user
   * @emits Notification on loading errors
   */
  async loadPlanFulfilledBy() {
    const fulfilledByUrl =
      this.apiServer +
      "/api/v2/report/planFulfilledByPlan?planHandleId=" +
      encodeURIComponent(this.projectId);
    const headers = await serverUtil.getHeaders(this.activeDid);
    try {
      const resp = await this.axios.get(fulfilledByUrl, { headers });
      if (resp.status === 200) {
        this.fulfilledByThis = resp.data.data;
      } else {
        this.notify.error(
          "Failed to retrieve plans fulfilled by this project.",
          TIMEOUTS.LONG,
        );
      }
    } catch (error: unknown) {
      const serverError = error as AxiosError;
      this.notify.error(
        "Something went wrong retrieving plans fulfilled by this project.",
        TIMEOUTS.LONG,
      );
      logger.error(
        "Error retrieving plans fulfilled by this project:",
        serverError.message,
      );
    }
  }

  /**
   * Handle clicking on a project entry found in the list
   * @param id of the project
   **/
  async onClickLoadProject(projectId: string) {
    const route = {
      path: "/project/" + encodeURIComponent(projectId),
    };
    this.$router.push(route);
    this.loadProject(projectId, this.activeDid);
  }

  getOpenStreetMapUrl() {
    // Google URL is https://maps.google.com/?q=LAT,LONG
    return (
      "https://www.openstreetmap.org/?mlat=" +
      this.latitude +
      "&mlon=" +
      this.longitude +
      "#map=15/" +
      this.latitude +
      "/" +
      this.longitude
    );
  }

  openGiftDialogToProject(
    contact?: libsUtil.GiverReceiverInputInfo | "Unnamed",
  ) {
    if (contact === "Unnamed") {
      // Special case: Pass undefined to trigger Step 1, but with "Unnamed" pre-selected
      (this.$refs.giveDialogToThis as GiftedDialog).open(
        undefined,
        undefined,
        undefined,
      );
      // Immediately select "Unnamed" and move to Step 2
      (this.$refs.giveDialogToThis as GiftedDialog).selectGiver();
    } else {
      // Open straight to Step 2 with current user as giver and current project as recipient
      (this.$refs.giveDialogToThis as GiftedDialog).open(
        {
          did: this.activeDid,
          name: "You",
        },
        {
          did: this.issuer,
          name: this.name,
          handleId: this.projectId,
          image: this.imageUrl,
        },
        undefined,
      );
    }
  }

  openGiftDialogFromProject() {
    // Set the project as giver and the current user as recipient
    (this.$refs.giveDialogFromThis as GiftedDialog).open(
      {
        did: undefined,
        name: this.name,
        handleId: this.projectId,
        image: this.imageUrl,
      },
      { did: this.activeDid, name: "You" },
      undefined,
      undefined,
      undefined,
    );
  }

  openOfferDialog() {
    (this.$refs.customOfferDialog as OfferDialog).open();
  }

  onClickAllContactsGifting() {
    const route = {
      name: "contact-gift",
      query: {
        projectId: this.projectId,
      },
    };
    this.$router.push(route);
  }

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

  checkIsFulfillable(offer: OfferSummaryRecord) {
    const offerRecord: GenericCredWrapper<OfferClaim> = {
      ...serverUtil.BLANK_GENERIC_SERVER_RECORD,
      claim: offer.fullClaim,
      claimType: "Offer",
      issuer: offer.offeredByDid,
    };
    return libsUtil.canFulfillOffer(offerRecord, this.isRegistered);
  }

  onClickFulfillGiveToOffer(offer: OfferSummaryRecord) {
    const offerClaimCred: GenericCredWrapper<OfferClaim> = {
      ...serverUtil.BLANK_GENERIC_SERVER_RECORD,
      claim: offer.fullClaim,
      issuer: offer.offeredByDid,
    };
    const giver: libsUtil.GiverReceiverInputInfo = {
      did: libsUtil.offerGiverDid(offerClaimCred),
    };
    (this.$refs.giveDialogToThis as GiftedDialog).open(
      giver,
      {
        did: offer.issuerDid,
        name: this.name,
        handleId: this.projectId,
        image: this.imageUrl,
      },
      offer.handleId,
      undefined,
      offer.objectDescription,
      offer.amount.toString(),
      offer.unit,
    );
  }

  // return an HTTPS URL if it's not a global URL
  ensureScheme(url: string) {
    if (!libsUtil.isGlobalUri(url)) {
      return "https://" + url;
    }
    return url;
  }

  // return just the domain for display, if possible
  domainForWebsite(url: string) {
    try {
      const hostname = new URL(url).hostname;
      if (!hostname) {
        // happens for non-http URLs
        return url;
      } else if (url.endsWith(hostname)) {
        // it's just the domain
        return hostname;
      } else {
        // there's more, but don't bother displaying the whole thing
        return hostname + "...";
      }
    } catch (error: unknown) {
      // must not be a valid URL
      return url;
    }
  }

  /**
   * @param confirmerIdList optional list of DIDs who confirmed; if missing, doesn't do a full server check
   */
  checkIsConfirmable(give: GiveSummaryRecord, confirmerIdList?: string[]) {
    const giveDetails: GenericCredWrapper<GiveActionClaim> = {
      ...serverUtil.BLANK_GENERIC_SERVER_RECORD,
      claim: give.fullClaim,
      claimType: "GiveAction",
      issuer: give.issuerDid,
    };
    return libsUtil.isGiveRecordTheUserCanConfirm(
      this.isRegistered,
      giveDetails,
      this.activeDid,
      confirmerIdList,
    );
  }

  shallowNotifyWhyCannotConfirm(give: GiveSummaryRecord) {
    const confirmerIds = this.recentlyCheckedAndUnconfirmableJwts.includes(
      give.jwtId,
    )
      ? [this.activeDid]
      : [];
    libsUtil.notifyWhyCannotConfirm(
      this.$notify,
      this.isRegistered,
      "GiveAction",
      give,
      this.activeDid,
      confirmerIds,
    );
  }

  async deepCheckConfirmable(give: GiveSummaryRecord) {
    this.checkingConfirmationForJwtId = give.jwtId;
    const confirmerInfo: libsUtil.ConfirmerData | undefined =
      await libsUtil.retrieveConfirmerIdList(
        this.apiServer,
        give.jwtId,
        give.issuerDid,
        this.activeDid,
      );
    if (
      this.checkIsConfirmable(give, confirmerInfo?.confirmerIdList as string[])
    ) {
      this.confirmConfirmClaim(give);
    } else {
      this.recentlyCheckedAndUnconfirmableJwts = [
        ...this.recentlyCheckedAndUnconfirmableJwts,
        give.jwtId,
      ];
      libsUtil.notifyWhyCannotConfirm(
        this.$notify,
        this.isRegistered,
        "GiveAction",
        give,
        this.activeDid,
        confirmerInfo?.confirmerIdList as string[],
      );
    }
    this.checkingConfirmationForJwtId = "";
  }

  confirmConfirmClaim(give: GiveSummaryRecord) {
    this.notify.confirm(
      NOTIFY_CONFIRM_CLAIM.text,
      async () => {
        await this.confirmClaim(give);
      },
      TIMEOUTS.MODAL,
    );
  }

  // similar code is found in ClaimView
  async confirmClaim(give: GiveSummaryRecord) {
    // similar logic is found in endorser-mobile
    const goodClaim = serverUtil.removeSchemaContext(
      serverUtil.removeVisibleToDids(
        serverUtil.addLastClaimOrHandleAsIdIfMissing(
          give.fullClaim,
          give.jwtId,
          give.handleId,
        ),
      ),
    );
    const confirmationClaim: GenericVerifiableCredential = {
      "@context": "https://schema.org",
      "@type": "AgreeAction",
      object: goodClaim,
    };
    const result = await serverUtil.createAndSubmitClaim(
      confirmationClaim,
      this.activeDid,
      this.apiServer,
      this.axios,
    );
    if (result.success) {
      this.notify.success("Confirmation submitted.", TIMEOUTS.LONG);
      this.recentlyCheckedAndUnconfirmableJwts = [
        ...this.recentlyCheckedAndUnconfirmableJwts,
        give.jwtId,
      ];
    } else {
      logger.error("Got error submitting the confirmation:", result);
      const message =
        (result.error as string) ||
        "There was a problem submitting the confirmation.";
      this.notify.error(message, TIMEOUTS.LONG);
    }
  }

  openHiddenDidDialog() {
    const shortestProjectId = this.projectId.startsWith(
      serverUtil.ENDORSER_CH_HANDLE_PREFIX,
    )
      ? this.projectId.substring(serverUtil.ENDORSER_CH_HANDLE_PREFIX.length)
      : this.projectId;
    (this.$refs.hiddenDidDialog as HiddenDidDialog).open(
      "project/" + shortestProjectId,
      "creator",
      this.issuerVisibleToDids,
      this.allContacts,
      this.activeDid,
      this.allMyDids,
    );
  }

  async loadTotals() {
    this.loadingTotals = true;
    const url =
      this.apiServer +
      "/api/v2/report/givesToPlans?planIds=" +
      encodeURIComponent(JSON.stringify([this.projectId]));
    const headers = await serverUtil.getHeaders(this.activeDid);

    try {
      const resp = await this.axios.get(url, { headers });
      if (resp.status === 200 && resp.data.data) {
        // Calculate totals by unit
        const totals: { [key: string]: number } = {};
        resp.data.data.forEach((give: GiveSummaryRecord) => {
          const amount = give.fullClaim.object?.amountOfThisGood;
          const unit = give.fullClaim.object?.unitCode;
          if (amount && unit) {
            totals[unit] = (totals[unit] || 0) + amount;
          }
        });

        // Convert totals object to array format
        this.givesTotalsByUnit = Object.entries(totals).map(
          ([unit, amount]) => ({
            unit,
            amount,
          }),
        );
      }
    } catch (error) {
      logger.error("Error loading totals:", error);
      this.notify.error(
        "Failed to load totals for this project.",
        TIMEOUTS.LONG,
      );
    } finally {
      this.loadingTotals = false;
    }
  }

  givenTotalHours(): number {
    return (
      this.givesTotalsByUnit.find((total) => total.unit === "HUR")?.amount || 0
    );
  }
}
</script>
