<template>
  <QuickNav selected="Profile" />
  <TopMessage />

  <!-- CONTENT -->
  <main
    id="Content"
    class="p-6 pb-24 max-w-3xl mx-auto"
    role="main"
    aria-label="Account Profile"
  >
    <!-- Heading -->
    <h1 id="ViewHeading" class="text-4xl text-center font-light">
      Your Identity
    </h1>

    <!-- ID notice -->
    <div
      v-if="!activeDid"
      id="noticeBeforeShare"
      class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3 mt-4"
      role="alert"
      aria-live="polite"
    >
      <p class="mb-4">
        <b>Note:</b> Before you can share with others or take any action, you
        need an identifier.
      </p>
      <router-link
        :to="{ name: 'start' }"
        class="inline-block text-md 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-4 py-2 rounded-md"
      >
        Create An Identifier
      </router-link>
    </div>

    <!-- Identity Details -->
    <IdentitySection
      :given-name="givenName"
      :profile-image-url="profileImageUrl"
      :active-did="activeDid"
      :is-registered="isRegistered"
      :show-large-identicon-id="showLargeIdenticonId"
      :show-large-identicon-url="showLargeIdenticonUrl"
      :show-did-copy="showDidCopy"
      @edit-name="onEditName"
      @show-qr-code="onShowQrCode"
      @add-image="onAddImage"
      @delete-image="onDeleteImage"
      @show-large-identicon-id="onShowLargeIdenticonId"
      @show-large-identicon-url="onShowLargeIdenticonUrl"
      @close-large-identicon="onCloseLargeIdenticon"
      @copy-did="onCopyDid"
    />

    <!-- Registration notice -->
    <RegistrationNotice
      :is-registered="isRegistered"
      :show="showRegistrationNotice"
      @share-info="onShareInfo"
    />

    <!-- Notifications -->
    <!-- Currently disabled because it doesn't work, even on Chrome. 
         If restored, make sure it works or doesn't show on mobile/electron. -->
    <section
      v-if="false"
      id="sectionNotifications"
      class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
      aria-labelledby="notificationsHeading"
    >
      <h2 id="notificationsHeading" class="mb-2 font-bold">Notifications</h2>
      <div class="flex items-center justify-between">
        <div>
          Reminder Notification
          <button
            class="text-slate-400 fa-fw cursor-pointer"
            aria-label="Learn more about reminder notifications"
            @click.stop="showReminderNotificationInfo"
          >
            <font-awesome
              icon="question-circle"
              aria-hidden="true"
            ></font-awesome>
          </button>
        </div>
        <div
          class="relative ml-2 cursor-pointer"
          role="switch"
          :aria-checked="notifyingReminder"
          aria-label="Toggle reminder notifications"
          tabindex="0"
          @click="showReminderNotificationChoice()"
        >
          <!-- input -->
          <input v-model="notifyingReminder" type="checkbox" class="sr-only" />
          <!-- line -->
          <div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
          <!-- dot -->
          <div
            class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
          ></div>
        </div>
      </div>
      <div v-if="notifyingReminder" class="w-full flex justify-between">
        <span class="ml-8 mr-8">Message: "{{ notifyingReminderMessage }}"</span>
        <span>{{ notifyingReminderTime.replace(" ", "&nbsp;") }}</span>
      </div>
      <div class="mt-2 flex items-center justify-between">
        <!-- label -->
        <div>
          New Activity Notification
          <font-awesome
            icon="question-circle"
            class="text-slate-400 fa-fw cursor-pointer"
            @click.stop="showNewActivityNotificationInfo"
          />
        </div>
        <!-- toggle -->
        <div
          class="relative ml-2 cursor-pointer"
          @click="showNewActivityNotificationChoice()"
        >
          <!-- input -->
          <input
            v-model="notifyingNewActivity"
            type="checkbox"
            class="sr-only"
          />
          <!-- line -->
          <div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
          <!-- dot -->
          <div
            class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
          ></div>
        </div>
      </div>
      <div v-if="notifyingNewActivityTime" class="w-full text-right">
        {{ notifyingNewActivityTime.replace(" ", "&nbsp;") }}
      </div>
      <div class="mt-2 text-center">
        <router-link class="text-sm text-blue-500" to="/help-notifications">
          Troubleshoot your notifications&hellip;
        </router-link>
      </div>
    </section>
    <PushNotificationPermission ref="pushNotificationPermission" />

    <LocationSearchSection :search-box="searchBox" />

    <!-- User Profile -->
    <section
      v-if="isRegistered"
      class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
      aria-labelledby="userProfileHeading"
    >
      <h2 id="userProfileHeading" class="mb-2 font-bold">
        Public Profile
        <button
          class="text-slate-400 fa-fw cursor-pointer"
          aria-label="Learn more about public profile"
          @click="showProfileInfo"
        >
          <font-awesome icon="circle-info" aria-hidden="true"></font-awesome>
        </button>
      </h2>
      <textarea
        v-model="userProfileDesc"
        class="w-full h-32 p-2 border border-slate-300 rounded-md"
        placeholder="Write something about yourself for the public..."
        :readonly="loadingProfile || savingProfile"
        :class="{ 'bg-slate-100': loadingProfile || savingProfile }"
        aria-label="Public profile description"
        :aria-busy="loadingProfile || savingProfile"
      ></textarea>

      <div class="flex items-center mb-4">
        <input
          v-model="includeUserProfileLocation"
          type="checkbox"
          class="mr-2"
          @change="onLocationCheckboxChange"
        />
        <label for="includeUserProfileLocation">Include Location</label>
      </div>
      <div v-if="includeUserProfileLocation" class="mb-4 aspect-video">
        <p class="text-sm mb-2 text-slate-500">
          The location you choose will be shared with the world until you remove
          this checkbox. For your security, choose a location nearby but not
          exactly at your true location, like at your town center.
        </p>

        <l-map
          ref="profileMap"
          class="!z-40 rounded-md"
          @click="onProfileMapClick"
          @ready="onMapReady"
          @mounted="onMapMounted"
        >
          <l-tile-layer
            url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
            layer-type="base"
            name="OpenStreetMap"
          />
          <l-marker
            v-if="userProfileLatitude && userProfileLongitude"
            :lat-lng="[userProfileLatitude, userProfileLongitude]"
            @click="confirmEraseLatLong()"
          />
        </l-map>
      </div>
      <div v-if="!loadingProfile && !savingProfile">
        <div class="grid grid-cols-1 sm:grid-cols-2 gap-2 mt-4">
          <button
            class="px-4 py-2 bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white rounded-md"
            :disabled="loadingProfile || savingProfile"
            :class="{
              'opacity-50 cursor-not-allowed': loadingProfile || savingProfile,
            }"
            @click="saveProfile"
          >
            Save Profile
          </button>
          <button
            class="px-4 py-2 bg-gradient-to-b from-red-400 to-red-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white rounded-md"
            :disabled="loadingProfile || savingProfile"
            :class="{
              'opacity-50 cursor-not-allowed':
                loadingProfile ||
                savingProfile ||
                (!userProfileDesc && !includeUserProfileLocation),
            }"
            @click="confirmDeleteProfile"
          >
            Delete Profile
          </button>
        </div>
      </div>
      <div v-else-if="loadingProfile">Loading...</div>
      <div v-else>Saving...</div>
    </section>

    <UsageLimitsSection
      v-if="activeDid"
      :loading-limits="loadingLimits"
      :limits-message="limitsMessage"
      :active-did="activeDid"
      :endorser-limits="endorserLimits"
      :image-limits="imageLimits"
      @recheck-limits="onRecheckLimits"
    />

    <DataExportSection :active-did="activeDid" />

    <!-- id used by puppeteer test script -->
    <h3
      data-testid="advancedSettings"
      class="text-blue-500 text-sm font-semibold mb-3 cursor-pointer"
      @click="toggleShowGeneralAdvanced"
    >
      {{
        showGeneralAdvanced
          ? "Hide Advanced Settings"
          : "Show Advanced Settings"
      }}
    </h3>
    <section
      v-if="showGeneralAdvanced"
      id="sectionAdvanced"
      aria-labelledby="advancedHeading"
    >
      <h2 id="advancedHeading" class="sr-only">Advanced Settings</h2>
      <p class="text-rose-600 mb-8">
        Beware: the features here can be confusing and even change data in ways
        you do not expect. But we support your freedom!
      </p>

      <!-- Deep Identity Details -->
      <span class="text-slate-500 text-sm font-bold mb-2">
        Identifier Details
      </span>
      <div
        id="sectionDeepIdentifier"
        class="bg-slate-100 rounded-md overflow-hidden px-4 py-3 mb-4"
      >
        <div class="text-slate-500 text-sm font-bold">Public Key (base 64)</div>
        <div
          class="text-sm text-slate-500 flex justify-start items-center mb-1"
        >
          <code class="truncate">{{ publicBase64 }}</code>
          <button
            class="ml-2"
            @click="
              doCopyTwoSecRedo(publicBase64, () => (showB64Copy = !showB64Copy))
            "
          >
            <font-awesome
              icon="copy"
              class="text-slate-400 fa-fw"
            ></font-awesome>
          </button>
          <span v-show="showB64Copy">Copied</span>
        </div>

        <div class="text-slate-500 text-sm font-bold">Public Key (hex)</div>
        <div
          class="text-sm text-slate-500 flex justify-start items-center mb-1"
        >
          <code class="truncate">{{ publicHex }}</code>
          <button
            class="ml-2"
            @click="
              doCopyTwoSecRedo(publicHex, () => (showPubCopy = !showPubCopy))
            "
          >
            <font-awesome
              icon="copy"
              class="text-slate-400 fa-fw"
            ></font-awesome>
          </button>
          <span v-show="showPubCopy">Copied</span>
        </div>

        <div class="text-slate-500 text-sm font-bold">Derivation Path</div>
        <div
          v-if="derivationPath"
          class="text-sm text-slate-500 flex justify-start items-center mb-1"
        >
          <code class="truncate">{{ derivationPath }}</code>
          <button
            class="ml-2"
            @click="
              doCopyTwoSecRedo(
                derivationPath,
                () => (showDerCopy = !showDerCopy),
              )
            "
          >
            <font-awesome
              icon="copy"
              class="text-slate-400 fa-fw"
            ></font-awesome>
          </button>
          <span v-show="showDerCopy">Copied</span>
        </div>
        <div
          v-else
          class="text-sm text-slate-500 flex justify-start items-center mb-1"
        >
          (none)
        </div>
      </div>

      <!-- id used by puppeteer test script -->
      <router-link
        id="switch-identity-link"
        :to="{ name: 'identity-switcher' }"
        class="block w-fit text-center text-md 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-4 py-2 rounded-md mb-2"
      >
        Switch Identifier
      </router-link>

      <div id="sectionImportContactsSettings" class="mt-4">
        <h2 class="text-slate-500 text-sm font-bold">Import Contacts</h2>

        <div class="ml-4 mt-2">
          <input type="file" class="ml-2" @change="uploadImportFile" />
          <transition
            enter-active-class="transform ease-out duration-300 transition"
            enter-from-class="translate-y-2 opacity-0 sm:translate-y-4"
            enter-to-class="translate-y-0 opacity-100 sm:translate-y-0"
            leave-active-class="transition ease-in duration-500"
            leave-from-class="opacity-100"
            leave-to-class="opacity-0"
          >
            <div v-if="showContactImport()" class="mt-4">
              <!-- Bulk import has an error
              <div class="flex justify-center">
                <button
                  class="block text-center text-md 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-1.5 py-2 rounded-md mb-6"
                  @click="confirmSubmitImportFile()"
                >
                  Overwrite Settings & Contacts
                  <br />
                  (which doesn't include Identifier Data)
                </button>
              </div>
              -->
              <div class="flex justify-center">
                <button
                  class="block text-center text-md 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-1.5 py-2 rounded-md mb-6"
                  @click="checkContactImports()"
                >
                  Import Contacts
                </button>
              </div>
            </div>
          </transition>
        </div>
      </div>

      <label
        for="toggleShowAmounts"
        class="flex items-center justify-between cursor-pointer my-4"
        @click="toggleShowContactAmounts"
      >
        <!-- label -->
        <span class="text-slate-500 text-sm font-bold">Contacts Display</span>
        <span class="ml-2">Show hours given & received</span>
        <!-- toggle -->
        <div class="relative ml-2">
          <!-- input -->
          <input
            v-model="showContactGives"
            type="checkbox"
            name="showContactGives"
            class="sr-only"
          />
          <!-- line -->
          <div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
          <!-- dot -->
          <div
            class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
          ></div>
        </div>
      </label>

      <div id="sectionClaimServer">
        <h2 class="text-slate-500 text-sm font-bold mt-4">Claim Server</h2>
        <div
          class="px-4 py-4"
          role="group"
          aria-labelledby="claimServerHeading"
        >
          <h3 id="claimServerHeading" class="sr-only">
            Claim Server Configuration
          </h3>
          <label for="apiServerInput" class="sr-only">API Server URL</label>
          <input
            id="apiServerInput"
            v-model="apiServerInput"
            type="text"
            class="block w-full rounded border border-slate-400 px-4 py-2"
            aria-describedby="apiServerDescription"
            placeholder="Enter API server URL"
          />
          <div id="apiServerDescription" class="sr-only" role="tooltip">
            Enter the URL for the claim server. You can use the buttons below to
            quickly set common server URLs.
          </div>
          <button
            v-if="apiServerInput != apiServer"
            class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
            aria-label="Save API server URL"
            @click="onClickSaveApiServer()"
          >
            <font-awesome
              icon="floppy-disk"
              class="fa-fw"
              color="white"
              aria-hidden="true"
            ></font-awesome>
          </button>
          <div class="mt-2" role="group" aria-label="Quick server selection">
            <button
              class="px-3 rounded bg-slate-200 border border-slate-400"
              aria-label="Use production server URL"
              @click="apiServerInput = AppConstants.PROD_ENDORSER_API_SERVER"
            >
              Use Prod
            </button>
            <button
              class="px-3 rounded bg-slate-200 border border-slate-400"
              aria-label="Use test server URL"
              @click="apiServerInput = AppConstants.TEST_ENDORSER_API_SERVER"
            >
              Use Test
            </button>
            <button
              class="px-3 rounded bg-slate-200 border border-slate-400"
              aria-label="Use local server URL"
              @click="apiServerInput = AppConstants.LOCAL_ENDORSER_API_SERVER"
            >
              Use Local
            </button>
          </div>
        </div>

        <label
          for="toggleProdWarningMessage"
          class="flex items-center justify-between cursor-pointer px-4 py-4"
          @click="toggleProdWarning"
        >
          <!-- label -->
          <h2>Show warning if on prod server</h2>
          <!-- toggle -->
          <div class="relative ml-2">
            <!-- input -->
            <input v-model="warnIfProdServer" type="checkbox" class="sr-only" />
            <!-- line -->
            <div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
            <!-- dot -->
            <div
              class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
            ></div>
          </div>
        </label>

        <label
          for="toggleTestWarningMessage"
          class="flex items-center justify-between cursor-pointer px-4 py-4"
          @click="toggleTestWarning"
        >
          <!-- label -->
          <h2>Show warning if on non-prod server</h2>
          <!-- toggle -->
          <div class="relative ml-2">
            <!-- input -->
            <input v-model="warnIfTestServer" type="checkbox" class="sr-only" />
            <!-- line -->
            <div class="block bg-slate-500 w-14 h-8 rounded-full"></div>
            <!-- dot -->
            <div
              class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
            ></div>
          </div>
        </label>
      </div>

      <h2 class="text-slate-500 text-sm font-bold mb-2">
        Notification Push Server
      </h2>
      <div class="px-3 py-4">
        <input
          v-model="webPushServerInput"
          type="text"
          class="block w-full rounded border border-slate-400 px-3 py-2"
        />
        <button
          v-if="webPushServerInput != webPushServer"
          class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
          @click="onClickSavePushServer()"
        >
          <font-awesome
            icon="floppy-disk"
            class="fa-fw"
            color="white"
          ></font-awesome>
        </button>
        <button
          class="px-3 rounded bg-slate-200 border border-slate-400"
          @click="webPushServerInput = AppConstants.PROD_PUSH_SERVER"
        >
          Use Prod
        </button>
        <button
          class="px-3 rounded bg-slate-200 border border-slate-400"
          @click="webPushServerInput = AppConstants.TEST1_PUSH_SERVER"
        >
          Use Test 1
        </button>
        <button
          class="px-3 rounded bg-slate-200 border border-slate-400"
          @click="webPushServerInput = AppConstants.TEST2_PUSH_SERVER"
        >
          Use Test 2
        </button>
      </div>
      <span v-if="!webPushServerInput" class="px-4 text-sm">
        When that setting is blank, this app will use the default web push
        server URL:
        {{ DEFAULT_PUSH_SERVER }}
      </span>

      <h2 class="text-slate-500 text-sm font-bold mb-2">Partner Server URL</h2>
      <div class="px-3 py-4">
        <input
          v-model="partnerApiServerInput"
          type="text"
          class="block w-full rounded border border-slate-400 px-3 py-2"
        />
        <button
          v-if="partnerApiServerInput != partnerApiServer"
          class="w-full px-4 rounded bg-yellow-500 border border-slate-400"
          @click="onClickSavePartnerServer()"
        >
          <font-awesome
            icon="floppy-disk"
            class="fa-fw"
            color="white"
          ></font-awesome>
        </button>
        <button
          class="px-3 rounded bg-slate-200 border border-slate-400"
          @click="partnerApiServerInput = AppConstants.PROD_PARTNER_API_SERVER"
        >
          Use Prod
        </button>
        <button
          class="px-3 rounded bg-slate-200 border border-slate-400"
          @click="partnerApiServerInput = AppConstants.TEST_PARTNER_API_SERVER"
        >
          Use Test
        </button>
        <button
          class="px-3 rounded bg-slate-200 border border-slate-400"
          @click="partnerApiServerInput = AppConstants.LOCAL_PARTNER_API_SERVER"
        >
          Use Local
        </button>
      </div>
      <span v-if="!partnerApiServerInput" class="px-4 text-sm">
        When that setting is blank, this app will use the default partner server
        URL:
        {{ DEFAULT_PARTNER_API_SERVER }}
      </span>

      <div class="mt-2">
        <span class="text-slate-500 text-sm font-bold">Image Server URL</span>
        &nbsp;
        <span class="text-sm">{{ DEFAULT_IMAGE_API_SERVER }}</span>
      </div>

      <label
        for="toggleHideRegisterPromptOnNewContact"
        class="flex items-center justify-between cursor-pointer mt-4"
        @click="toggleHideRegisterPromptOnNewContact()"
      >
        <!-- label -->
        <span class="text-slate-500 text-sm font-bold">
          Hide Register Prompt on New Contact
        </span>
        <!-- toggle -->
        <div class="relative ml-2">
          <!-- input -->
          <input
            v-model="hideRegisterPromptOnNewContact"
            type="checkbox"
            class="sr-only"
          />
          <!-- line -->
          <div class="block bg-slate-500 w-14 h-8 rounded-full" />
          <!-- dot -->
          <div
            class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
          />
        </div>
      </label>

      <label
        for="toggleShowShortcutBvc"
        class="flex items-center justify-between cursor-pointer mt-4"
        @click="toggleShowShortcutBvc"
      >
        <!-- label -->
        <span class="text-slate-500 text-sm font-bold">
          Show BVC Shortcut on Home Page
        </span>
        <!-- toggle -->
        <div class="relative ml-2">
          <!-- input -->
          <input v-model="showShortcutBvc" type="checkbox" class="sr-only" />
          <!-- line -->
          <div class="block bg-slate-500 w-14 h-8 rounded-full" />
          <!-- dot -->
          <div
            class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
          />
        </div>
      </label>

      <div id="sectionPasskeyExpiration" class="flex mt-4 justify-between">
        <span>
          <span class="text-slate-500 text-sm font-bold">
            Passkey Expiration Minutes
          </span>
          <br />
          <span class="text-sm ml-2">
            {{ passkeyExpirationDescription }}
          </span>
        </span>
        <div class="relative ml-2">
          <input
            v-model="passkeyExpirationMinutes"
            type="number"
            class="border border-slate-400 rounded px-2 py-2 text-center w-20"
            @change="updatePasskeyExpiration"
          />
        </div>
      </div>

      <label
        for="toggleShowGeneralAdvanced"
        class="flex items-center justify-between cursor-pointer mt-4"
        @click="toggleShowGeneralAdvanced"
      >
        <!-- label -->
        <span class="text-slate-500 text-sm font-bold">
          Show All General Advanced Functions
        </span>
        <!-- toggle -->
        <div class="relative ml-2">
          <!-- input -->
          <input
            v-model="showGeneralAdvanced"
            type="checkbox"
            class="sr-only"
          />
          <!-- line -->
          <div class="block bg-slate-500 w-14 h-8 rounded-full" />
          <!-- dot -->
          <div
            class="dot absolute left-1 top-1 bg-slate-400 w-6 h-6 rounded-full transition"
          />
        </div>
      </label>

      <router-link
        :to="{ name: 'logs' }"
        class="block w-fit text-center text-md 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-4 py-2 rounded-md mt-2"
      >
        Logs
      </router-link>
      <router-link
        :to="{ name: 'test' }"
        class="block w-fit text-center text-md 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-4 py-2 rounded-md mt-2"
      >
        Test Page
      </router-link>

      <div class="flex mt-2">
        <button>
          <router-link
            :to="{ name: 'statistics' }"
            class="block w-fit text-center text-md 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-4 py-2 rounded-md"
          >
            See Global Animated History of Giving
          </router-link>
        </button>
      </div>
    </section>
  </main>

  <UserNameDialog ref="userNameDialog" />
  <ImageMethodDialog ref="imageMethodDialog" />
</template>

<script lang="ts">
import "leaflet/dist/leaflet.css";

import { Buffer } from "buffer/";
import "dexie-export-import";
// @ts-expect-error - they aren't exporting it but it's there
import { ImportProgress } from "dexie-export-import";
import { LeafletMouseEvent } from "leaflet";
import * as L from "leaflet";
import * as R from "ramda";
import { IIdentifier } from "@veramo/core";
import { ref } from "vue";
import { Component, Vue } from "vue-facing-decorator";
import { RouteLocationNormalizedLoaded, Router } from "vue-router";
import { useClipboard } from "@vueuse/core";
import { LMap, LMarker, LTileLayer } from "@vue-leaflet/vue-leaflet";
import { Capacitor } from "@capacitor/core";

import EntityIcon from "../components/EntityIcon.vue";
import ImageMethodDialog from "../components/ImageMethodDialog.vue";
import PushNotificationPermission from "../components/PushNotificationPermission.vue";
import QuickNav from "../components/QuickNav.vue";
import TopMessage from "../components/TopMessage.vue";
import UserNameDialog from "../components/UserNameDialog.vue";
import DataExportSection from "../components/DataExportSection.vue";
import IdentitySection from "@/components/IdentitySection.vue";
import RegistrationNotice from "@/components/RegistrationNotice.vue";
import LocationSearchSection from "@/components/LocationSearchSection.vue";
import UsageLimitsSection from "@/components/UsageLimitsSection.vue";
import {
  AppString,
  DEFAULT_IMAGE_API_SERVER,
  DEFAULT_PARTNER_API_SERVER,
  DEFAULT_PUSH_SERVER,
  IMAGE_TYPE_PROFILE,
  NotificationIface,
} from "../constants/app";
import { Contact } from "../db/tables/contacts";
import {
  DEFAULT_PASSKEY_EXPIRATION_MINUTES,
  BoundingBox,
} from "../db/tables/settings";
import { EndorserRateLimits, ImageRateLimits } from "../interfaces";

import {
  clearPasskeyToken,
  fetchEndorserRateLimits,
  fetchImageRateLimits,
  getHeaders,
  tokenExpiryTimeDescription,
} from "../libs/endorserServer";
import {
  DAILY_CHECK_TITLE,
  DIRECT_PUSH_TITLE,
  retrieveAccountMetadata,
} from "../libs/util";
import { logger } from "../utils/logger";
import { PlatformServiceMixin } from "../utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import { ACCOUNT_VIEW_CONSTANTS } from "@/constants/accountView";
import {
  AccountSettings,
  isApiError,
  ImportContent,
} from "@/interfaces/accountView";
import {
  ProfileService,
  createProfileService,
  ProfileData,
} from "@/services/ProfileService";

const inputImportFileNameRef = ref<Blob>();

interface UserNameDialogRef {
  open: (cb: (name?: string) => void) => void;
}

@Component({
  components: {
    EntityIcon,
    ImageMethodDialog,
    LMap,
    LMarker,
    LTileLayer,
    PushNotificationPermission,
    QuickNav,
    TopMessage,
    UserNameDialog,
    DataExportSection,
    IdentitySection,
    RegistrationNotice,
    LocationSearchSection,
    UsageLimitsSection,
  },
  mixins: [PlatformServiceMixin],
})
export default class AccountViewView extends Vue {
  $notify!: (notification: NotificationIface, timeout?: number) => void;
  $route!: RouteLocationNormalizedLoaded;
  $router!: Router;

  // Constants
  readonly AppConstants: typeof AppString = AppString;
  readonly DEFAULT_PUSH_SERVER: string = DEFAULT_PUSH_SERVER;
  readonly DEFAULT_IMAGE_API_SERVER: string = DEFAULT_IMAGE_API_SERVER;
  readonly DEFAULT_PARTNER_API_SERVER: string = DEFAULT_PARTNER_API_SERVER;

  // Identity and settings properties
  activeDid: string = "";
  apiServer: string = "";
  apiServerInput: string = "";
  derivationPath: string = "";
  givenName: string = "";
  hideRegisterPromptOnNewContact: boolean = false;
  isRegistered: boolean = false;
  isSearchAreasSet: boolean = false;
  searchBox: { name: string; bbox: BoundingBox } | null = null;
  partnerApiServer: string = DEFAULT_PARTNER_API_SERVER;
  partnerApiServerInput: string = DEFAULT_PARTNER_API_SERVER;
  passkeyExpirationDescription: string = "";
  passkeyExpirationMinutes: number = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
  previousPasskeyExpirationMinutes: number = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
  profileImageUrl?: string;
  publicHex: string = "";
  publicBase64: string = "";
  webPushServer: string = DEFAULT_PUSH_SERVER;
  webPushServerInput: string = DEFAULT_PUSH_SERVER;

  // Profile properties
  userProfileDesc: string = "";
  userProfileLatitude: number = 0;
  userProfileLongitude: number = 0;
  includeUserProfileLocation: boolean = false;
  savingProfile: boolean = false;

  // Notification properties
  notifyingNewActivity: boolean = false;
  notifyingNewActivityTime: string = "";
  notifyingReminder: boolean = false;
  notifyingReminderMessage: string = "";
  notifyingReminderTime: string = "";
  subscription: PushSubscription | null = null;

  // UI state properties
  downloadUrl: string = ""; // because DuckDuckGo doesn't download on automated call to "click" on the anchor
  loadingLimits: boolean = false;
  loadingProfile: boolean = true;
  showAdvanced: boolean = false;
  showB64Copy: boolean = false;
  showContactGives: boolean = false;
  showDidCopy: boolean = false;
  showDerCopy: boolean = false;
  showGeneralAdvanced: boolean = false;
  showLargeIdenticonId?: string;
  showLargeIdenticonUrl?: string;
  showPubCopy: boolean = false;
  showShortcutBvc: boolean = false;
  warnIfProdServer: boolean = false;
  warnIfTestServer: boolean = false;
  zoom: number = 2;
  isMapReady: boolean = false;

  // Limits and validation properties
  endorserLimits: EndorserRateLimits | null = null;
  imageLimits: ImageRateLimits | null = null;
  limitsMessage: string = "";

  private profileService!: ProfileService;
  private notify!: ReturnType<typeof createNotifyHelpers>;

  created() {
    this.notify = createNotifyHelpers(this.$notify);

    // Fix Leaflet icon issues in modern bundlers
    // This prevents the "Cannot read properties of undefined (reading 'Default')" error
    if (L.Icon.Default) {
      // Type-safe way to handle Leaflet icon prototype
      const iconDefault = L.Icon.Default.prototype as Record<string, unknown>;
      if ("_getIconUrl" in iconDefault) {
        delete iconDefault._getIconUrl;
      }
      L.Icon.Default.mergeOptions({
        iconRetinaUrl:
          "https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon-2x.png",
        iconUrl: "https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png",
        shadowUrl:
          "https://unpkg.com/leaflet@1.7.1/dist/images/marker-shadow.png",
      });
    }
  }

  /**
   * Async function executed when the component is mounted.
   * Initializes the component's state with values from the database,
   * handles identity-related tasks, and checks limitations.
   *
   * @throws Will display specific messages to the user based on different errors.
   */
  async mounted(): Promise<void> {
    this.profileService = createProfileService(
      this.axios,
      this.partnerApiServer,
    );
    try {
      await this.initializeState();
      await this.processIdentity();

      if (this.isRegistered) {
        try {
          const profile = await this.profileService.loadProfile(this.activeDid);
          if (profile) {
            this.userProfileDesc = profile.description;
            this.userProfileLatitude = profile.latitude;
            this.userProfileLongitude = profile.longitude;
            this.includeUserProfileLocation = profile.includeLocation;

            // Initialize map ready state if location is included
            if (profile.includeLocation) {
              this.isMapReady = false; // Will be set to true when map is ready
            }
          } else {
            // Profile not created yet; leave defaults
          }
        } catch (error) {
          logger.error("Error loading profile:", error);
          this.notify.error(
            ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_NOT_AVAILABLE,
          );
        }
      }
    } catch (error) {
      logger.error(
        "Telling user to clear cache at page create because:",
        error,
      );
      logger.error(
        "To repeat with concatenated error: telling user to clear cache at page create because: " +
          error,
      );
      this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_LOAD_ERROR);
    } finally {
      this.loadingProfile = false;
    }

    // Check limits for any user with an activeDid (this will also check registration status)
    if (this.activeDid) {
      await this.checkLimits();
    }

    // Only check service worker on web platform - Capacitor/Electron don't support it
    if (!Capacitor.isNativePlatform()) {
      try {
        /**
         * Service workers only exist on web platforms
         */
        const registration = await navigator.serviceWorker?.ready;
        this.subscription = await registration.pushManager.getSubscription();
        if (!this.subscription) {
          if (this.notifyingNewActivity || this.notifyingReminder) {
            // the app thought there was a subscription but there isn't, so fix the settings
            this.turnOffNotifyingFlags();
          }
        }
      } catch (error) {
        this.notify.warning(
          ACCOUNT_VIEW_CONSTANTS.ERRORS.BROWSER_NOTIFICATIONS_UNSUPPORTED,
          TIMEOUTS.VERY_LONG,
        );
      }
    } else {
      // On native platforms (Capacitor/Electron), skip service worker checks
      // Native notifications are handled differently
      this.subscription = null;
    }
    this.passkeyExpirationDescription = tokenExpiryTimeDescription();
  }

  beforeUnmount(): void {
    if (this.downloadUrl) {
      URL.revokeObjectURL(this.downloadUrl);
    }
  }

  /**
   * Initializes component state with values from the database or defaults.
   */
  async initializeState(): Promise<void> {
    // Then get the account-specific settings
    const settings: AccountSettings = await this.$accountSettings();

    this.activeDid = settings.activeDid || "";
    this.apiServer = settings.apiServer || "";
    this.apiServerInput = settings.apiServer || "";
    this.givenName =
      (settings?.firstName || "") +
      (settings?.lastName ? ` ${settings.lastName}` : ""); // pre v 0.1.3
    this.hideRegisterPromptOnNewContact =
      !!settings.hideRegisterPromptOnNewContact;
    this.isRegistered = !!settings?.isRegistered;
    this.isSearchAreasSet = !!settings.searchBoxes;
    this.searchBox = settings.searchBoxes?.[0] || null;
    this.notifyingNewActivity = !!settings.notifyingNewActivityTime;
    this.notifyingNewActivityTime = settings.notifyingNewActivityTime || "";
    this.notifyingReminder = !!settings.notifyingReminderTime;
    this.notifyingReminderMessage = settings.notifyingReminderMessage || "";
    this.notifyingReminderTime = settings.notifyingReminderTime || "";
    this.partnerApiServer = settings.partnerApiServer || this.partnerApiServer;
    this.partnerApiServerInput =
      settings.partnerApiServer || this.partnerApiServerInput;
    this.profileImageUrl = settings.profileImageUrl;
    this.showContactGives = !!settings.showContactGivesInline;
    this.passkeyExpirationMinutes =
      settings.passkeyExpirationMinutes ?? DEFAULT_PASSKEY_EXPIRATION_MINUTES;
    this.previousPasskeyExpirationMinutes = this.passkeyExpirationMinutes;
    this.showGeneralAdvanced = !!settings.showGeneralAdvanced;
    this.showShortcutBvc = !!settings.showShortcutBvc;
    this.warnIfProdServer = !!settings.warnIfProdServer;
    this.warnIfTestServer = !!settings.warnIfTestServer;
    this.webPushServer = settings.webPushServer || this.webPushServer;
    this.webPushServerInput = settings.webPushServer || this.webPushServerInput;
  }

  // call fn, copy text to the clipboard, then redo fn after 2 seconds
  doCopyTwoSecRedo(text: string, fn: () => void): void {
    fn();
    useClipboard()
      .copy(text)
      .then(() => setTimeout(fn, 2000));
  }

  async toggleShowContactAmounts(): Promise<void> {
    this.showContactGives = !this.showContactGives;
    await this.$saveSettings({
      showContactGivesInline: this.showContactGives,
    });
  }

  async toggleShowGeneralAdvanced(): Promise<void> {
    this.showGeneralAdvanced = !this.showGeneralAdvanced;
    await this.$saveSettings({
      showGeneralAdvanced: this.showGeneralAdvanced,
    });
  }

  async toggleProdWarning(): Promise<void> {
    this.warnIfProdServer = !this.warnIfProdServer;
    await this.$saveSettings({
      warnIfProdServer: this.warnIfProdServer,
    });
  }

  async toggleTestWarning(): Promise<void> {
    this.warnIfTestServer = !this.warnIfTestServer;
    await this.$saveSettings({
      warnIfTestServer: this.warnIfTestServer,
    });
  }

  async toggleShowShortcutBvc(): Promise<void> {
    this.showShortcutBvc = !this.showShortcutBvc;
    await this.$saveSettings({
      showShortcutBvc: this.showShortcutBvc,
    });
  }

  readableDate(timeStr: string): string {
    return timeStr ? timeStr.substring(0, timeStr.indexOf("T")) : "?";
  }

  /**
   * Processes the identity and updates the component's state.
   */
  async processIdentity(): Promise<void> {
    const account = await retrieveAccountMetadata(this.activeDid);
    if (account?.identity) {
      const identity = JSON.parse(account.identity as string) as IIdentifier;
      this.publicHex = identity.keys[0].publicKeyHex;
      this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
      this.derivationPath = identity.keys[0].meta?.derivationPath as string;
    } else if (account?.publicKeyHex) {
      // use the backup values in the top level of the account object
      this.publicHex = account.publicKeyHex as string;
      this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
      this.derivationPath = account.derivationPath as string;
    }
  }

  async showNewActivityNotificationInfo(): Promise<void> {
    this.notify.confirm(
      ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.NEW_ACTIVITY_INFO,
      async () => {
        await (this.$router as Router).push({
          name: "help-notification-types",
        });
      },
    );
  }

  async showNewActivityNotificationChoice(): Promise<void> {
    if (!this.notifyingNewActivity) {
      (
        this.$refs.pushNotificationPermission as PushNotificationPermission
      ).open(DAILY_CHECK_TITLE, async (success: boolean, timeText: string) => {
        if (success) {
          await this.$saveSettings({
            notifyingNewActivityTime: timeText,
          });
          this.notifyingNewActivity = true;
          this.notifyingNewActivityTime = timeText;
        }
      });
    } else {
      this.notify.notificationOff(DAILY_CHECK_TITLE, async (success) => {
        if (success) {
          await this.$saveSettings({
            notifyingNewActivityTime: "",
          });
          this.notifyingNewActivity = false;
          this.notifyingNewActivityTime = "";
        }
      });
    }
  }

  async showReminderNotificationInfo(): Promise<void> {
    this.notify.confirm(
      ACCOUNT_VIEW_CONSTANTS.NOTIFICATIONS.REMINDER_INFO,
      async () => {
        await (this.$router as Router).push({
          name: "help-notification-types",
        });
      },
    );
  }

  async showReminderNotificationChoice(): Promise<void> {
    if (!this.notifyingReminder) {
      (
        this.$refs.pushNotificationPermission as PushNotificationPermission
      ).open(
        DIRECT_PUSH_TITLE,
        async (success: boolean, timeText: string, message?: string) => {
          if (success) {
            await this.$saveSettings({
              notifyingReminderMessage: message,
              notifyingReminderTime: timeText,
            });
            this.notifyingReminder = true;
            this.notifyingReminderMessage = message || "";
            this.notifyingReminderTime = timeText;
          }
        },
      );
    } else {
      this.notify.notificationOff(DIRECT_PUSH_TITLE, async (success) => {
        if (success) {
          await this.$saveSettings({
            notifyingReminderMessage: "",
            notifyingReminderTime: "",
          });
          this.notifyingReminder = false;
          this.notifyingReminderMessage = "";
          this.notifyingReminderTime = "";
        }
      });
    }
  }

  public async toggleHideRegisterPromptOnNewContact(): Promise<void> {
    const newSetting = !this.hideRegisterPromptOnNewContact;
    await this.$saveSettings({
      hideRegisterPromptOnNewContact: newSetting,
    });
    this.hideRegisterPromptOnNewContact = newSetting;
  }

  public async updatePasskeyExpiration(): Promise<void> {
    await this.$saveSettings({
      passkeyExpirationMinutes: this.passkeyExpirationMinutes,
    });
    clearPasskeyToken();
    this.passkeyExpirationDescription = tokenExpiryTimeDescription();
  }

  public async turnOffNotifyingFlags(): Promise<void> {
    // should tell the push server as well
    await this.$saveSettings({
      notifyingNewActivityTime: "",
      notifyingReminderMessage: "",
      notifyingReminderTime: "",
    });
    this.notifyingNewActivity = false;
    this.notifyingNewActivityTime = "";
    this.notifyingReminder = false;
    this.notifyingReminderMessage = "";
    this.notifyingReminderTime = "";
  }

  /**
   * Asynchronously exports the database into a downloadable JSON file.
   *
   * @throws Will notify the user if there is an export error.
   */
  public async exportDatabase() {
    try {
      // Generate the blob from the database
      const blob = await this.generateDatabaseBlob();

      // Create a temporary URL for the blob
      this.downloadUrl = this.createBlobURL(blob);

      // Trigger the download
      this.downloadDatabaseBackup(this.downloadUrl);

      // Notify the user that the download has started
      this.notifyDownloadStarted();

      // Revoke the temporary URL -- after a pause to avoid DuckDuckGo download failure
      setTimeout(() => URL.revokeObjectURL(this.downloadUrl), 1000);
    } catch (error) {
      this.handleExportError(error);
    }
  }

  /**
   * Generates a blob object representing the database.
   *
   * @returns {Promise<Blob>} The generated blob object.
   */
  private async generateDatabaseBlob(): Promise<Blob> {
    // TODO: Implement this for SQLite
    throw new Error("Not implemented");
  }

  /**
   * Creates a temporary URL for a blob object.
   *
   * @param {Blob} blob - The blob object.
   * @returns {string} The temporary URL for the blob.
   */
  private createBlobURL(blob: Blob): string {
    return URL.createObjectURL(blob);
  }

  /**
   * Triggers the download of the database backup.
   *
   * @param {string} url - The temporary URL for the blob.
   */
  private downloadDatabaseBackup(url: string) {
    const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement;
    downloadAnchor.href = url;
    downloadAnchor.download = `${AppString.APP_NAME_NO_SPACES}-backup.json`;
    downloadAnchor.click(); // doesn't work for some browsers, eg. DuckDuckGo
  }

  public computedStartDownloadLinkClassNames() {
    return {
      hidden: this.downloadUrl,
    };
  }

  public computedDownloadLinkClassNames() {
    return {
      hidden: !this.downloadUrl,
    };
  }

  /**
   * Notifies the user that the download has started.
   */
  private notifyDownloadStarted() {
    this.notify.downloadStarted();
  }

  /**
   * Handles errors during the database export process.
   *
   * @param {Error} error - The error object.
   */
  private handleExportError(error: unknown): void {
    logger.error("Export Error:", error);
    this.notify.error(
      ACCOUNT_VIEW_CONSTANTS.ERRORS.EXPORT_ERROR,
      TIMEOUTS.STANDARD,
    );
  }

  async uploadImportFile(event: Event): Promise<void> {
    inputImportFileNameRef.value = (
      event.target as HTMLInputElement
    ).files?.[0];
  }

  showContactImport(): boolean {
    return !!inputImportFileNameRef.value;
  }

  confirmSubmitImportFile(): void {
    if (inputImportFileNameRef.value != null) {
      this.notify.confirm(
        ACCOUNT_VIEW_CONSTANTS.WARNINGS.IMPORT_REPLACE_WARNING,
        this.submitImportFile,
      );
    }
  }

  /**
   * Asynchronously imports the database from a downloadable JSON file.
   *
   * @throws Will notify the user if there is an export error.
   */
  async submitImportFile(): Promise<void> {
    if (inputImportFileNameRef.value != null) {
      // TODO: implement this for SQLite
    }
  }

  async checkContactImports(): Promise<void> {
    const reader = new FileReader();
    reader.onload = (event) => {
      const fileContent: string = (event.target?.result as string) || "{}";
      try {
        const contents: ImportContent = JSON.parse(fileContent);
        const contactTableRows: Array<Contact> = (
          contents.data?.data as [{ tableName: string; rows: Array<Contact> }]
        )?.find((table) => table.tableName === "contacts")
          ?.rows as Array<Contact>;
        const contactRows = contactTableRows.map(
          // @ts-expect-error for omitting this field that is found in the Dexie format
          (contact) => R.omit(["$types"], contact) as Contact,
        );
        (this.$router as Router).push({
          name: "contact-import",
          query: { contacts: JSON.stringify(contactRows) },
        });
      } catch (error) {
        logger.error("Error checking contact imports:", error);
        this.notify.error(
          ACCOUNT_VIEW_CONSTANTS.ERRORS.IMPORT_ERROR,
          TIMEOUTS.STANDARD,
        );
      }
    };
    reader.readAsText(inputImportFileNameRef.value as Blob);
  }

  private progressCallback(progress: ImportProgress): boolean {
    logger.log(
      `Import progress: ${progress.completedRows} of ${progress.totalRows} rows completed.`,
    );
    if (progress.done) {
      this.notify.success(
        ACCOUNT_VIEW_CONSTANTS.SUCCESS.IMPORT_COMPLETE,
        TIMEOUTS.LONG,
      );
    }
    return true;
  }

  async checkLimits(): Promise<void> {
    this.loadingLimits = true;
    try {
      const did = this.activeDid;

      if (!did) {
        this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IDENTIFIER;
        return;
      }

      await this.$saveUserSettings(did, {
        apiServer: this.apiServer,
        partnerApiServer: this.partnerApiServer,
        webPushServer: this.webPushServer,
      });

      const imageResp = await fetchImageRateLimits(this.axios, did);

      if (imageResp.status === 200) {
        this.imageLimits = imageResp.data;
      } else {
        this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_IMAGE_ACCESS;
        this.notify.warning(ACCOUNT_VIEW_CONSTANTS.LIMITS.CANNOT_UPLOAD_IMAGES);
        return;
      }

      const endorserResp = await fetchEndorserRateLimits(
        this.apiServer,
        this.axios,
        did,
      );

      if (endorserResp.status === 200) {
        this.endorserLimits = endorserResp.data;
      } else {
        this.limitsMessage = ACCOUNT_VIEW_CONSTANTS.LIMITS.NO_LIMITS_FOUND;
        this.notify.warning(ACCOUNT_VIEW_CONSTANTS.LIMITS.BAD_SERVER_RESPONSE);
        return;
      }
    } catch (error) {
      this.limitsMessage =
        ACCOUNT_VIEW_CONSTANTS.LIMITS.ERROR_RETRIEVING_LIMITS;
      logger.error("Error retrieving limits: ", error);
      // this.notify.error(this.limitsMessage, TIMEOUTS.STANDARD);
    } finally {
      this.loadingLimits = false;
    }
  }

  async onClickSaveApiServer(): Promise<void> {
    await this.$saveSettings({
      apiServer: this.apiServerInput,
    });
    this.apiServer = this.apiServerInput;
    // Add this line to save to user-specific settings
    await this.$saveUserSettings(this.activeDid, {
      apiServer: this.apiServer,
    });
  }

  async onClickSavePartnerServer(): Promise<void> {
    await this.$saveSettings({
      partnerApiServer: this.partnerApiServerInput,
    });
    this.partnerApiServer = this.partnerApiServerInput;
    await this.$saveUserSettings(this.activeDid, {
      partnerApiServer: this.partnerApiServer,
    });
  }

  async onClickSavePushServer(): Promise<void> {
    await this.$saveSettings({
      webPushServer: this.webPushServerInput,
    });
    this.webPushServer = this.webPushServerInput;
    this.notify.warning(ACCOUNT_VIEW_CONSTANTS.INFO.RELOAD_VAPID);
  }

  openImageDialog(): void {
    (this.$refs.imageMethodDialog as ImageMethodDialog).open(
      async (imgUrl) => {
        await this.$saveSettings({
          profileImageUrl: imgUrl,
        });
        this.profileImageUrl = imgUrl;
      },
      IMAGE_TYPE_PROFILE,
      true,
    );
  }

  confirmDeleteImage(): void {
    this.notify.confirm(
      ACCOUNT_VIEW_CONSTANTS.WARNINGS.IMAGE_DELETE_WARNING,
      this.deleteImage,
    );
  }

  async deleteImage(): Promise<void> {
    try {
      // Extract the image ID from the full URL
      const imageId = this.profileImageUrl?.split("/").pop();
      if (!imageId) {
        this.notify.error("Invalid image URL");
        return;
      }

      const response = await this.axios.delete(
        this.apiServer + "/api/image/" + imageId,
        { headers: await getHeaders(this.activeDid) },
      );
      if (response.status === 204) {
        this.profileImageUrl = "";
        await this.$saveSettings({
          profileImageUrl: "",
        });
        this.notify.success("Image deleted successfully.");
      } else {
        logger.error("Non-success deleting image:", response);
        this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.IMAGE_DELETE_PROBLEM);
        // keep the imageUrl in localStorage so the user can try again if they want
      }
    } catch (error) {
      if (isApiError(error) && error.response?.status === 404) {
        // it already doesn't exist so we won't say anything to the user
        // Clear the local reference since the image is gone
        this.profileImageUrl = "";
        await this.$saveSettings({
          profileImageUrl: "",
        });
      } else {
        this.notify.error(
          ACCOUNT_VIEW_CONSTANTS.ERRORS.IMAGE_DELETE_ERROR,
          TIMEOUTS.STANDARD,
        );
      }
    }
  }

  onMapReady(map: L.Map): void {
    try {
      logger.debug("Map ready event fired, map object:", map);
      // doing this here instead of on the l-map element avoids a recentering after a drag then zoom at startup
      const zoom =
        this.userProfileLatitude && this.userProfileLongitude ? 12 : 2;
      const lat = this.userProfileLatitude || 0;
      const lng = this.userProfileLongitude || 0;
      map.setView([lat, lng], zoom);
      this.isMapReady = true;
      logger.debug(
        "Map ready state set to true, coordinates:",
        [lat, lng],
        "zoom:",
        zoom,
      );
    } catch (error) {
      logger.error("Error in onMapReady:", error);
      this.isMapReady = true; // Set to true even on error to prevent infinite loading
    }
  }

  onMapMounted(): void {
    logger.debug("Map component mounted");
    // Check if map ref is available
    const mapRef = this.$refs.profileMap;
    logger.debug("Map ref:", mapRef);

    // Try to set map ready after component is mounted
    setTimeout(() => {
      this.isMapReady = true;
      logger.debug("Map ready set to true after mounted");
    }, 500);
  }

  // Fallback method to handle map initialization failures
  private handleMapInitFailure(): void {
    logger.debug("Starting map initialization timeout (5 seconds)");
    setTimeout(() => {
      if (!this.isMapReady) {
        logger.warn("Map failed to initialize, forcing ready state");
        this.isMapReady = true;
      } else {
        logger.debug("Map initialized successfully, timeout not needed");
      }
    }, 5000); // 5 second timeout
  }

  showProfileInfo(): void {
    this.notify.info(
      ACCOUNT_VIEW_CONSTANTS.INFO.PROFILE_INFO,
      TIMEOUTS.VERY_LONG,
    );
  }

  async saveProfile(): Promise<void> {
    this.savingProfile = true;
    try {
      const profileData: ProfileData = {
        description: this.userProfileDesc,
        latitude: this.userProfileLatitude,
        longitude: this.userProfileLongitude,
        includeLocation: this.includeUserProfileLocation,
      };

      logger.debug("Saving profile data:", profileData);

      const success = await this.profileService.saveProfile(
        this.activeDid,
        profileData,
      );
      if (success) {
        this.notify.success(ACCOUNT_VIEW_CONSTANTS.SUCCESS.PROFILE_SAVED);
      } else {
        this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_SAVE_ERROR);
      }
    } catch (error) {
      logger.error("Error saving profile:", error);
      this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_SAVE_ERROR);
    } finally {
      this.savingProfile = false;
    }
  }

  toggleUserProfileLocation(): void {
    try {
      const updated = this.profileService.toggleProfileLocation({
        description: this.userProfileDesc,
        latitude: this.userProfileLatitude,
        longitude: this.userProfileLongitude,
        includeLocation: this.includeUserProfileLocation,
      });
      this.userProfileLatitude = updated.latitude;
      this.userProfileLongitude = updated.longitude;
      this.includeUserProfileLocation = updated.includeLocation;

      // Reset map ready state when toggling location
      if (!updated.includeLocation) {
        this.isMapReady = false;
      }
    } catch (error) {
      logger.error("Error in toggleUserProfileLocation:", error);
      this.notify.error("Failed to toggle location setting");
    }
  }

  confirmEraseLatLong(): void {
    this.notify.confirm(
      ACCOUNT_VIEW_CONSTANTS.WARNINGS.ERASE_LOCATION_WARNING,
      async () => {
        this.eraseLatLong();
      },
    );
  }

  eraseLatLong(): void {
    this.userProfileLatitude = 0;
    this.userProfileLongitude = 0;
    this.zoom = 2;
    this.includeUserProfileLocation = false;
  }

  async confirmDeleteProfile(): Promise<void> {
    this.notify.confirm(
      ACCOUNT_VIEW_CONSTANTS.WARNINGS.DELETE_PROFILE_WARNING,
      this.deleteProfile,
    );
  }

  async deleteProfile(): Promise<void> {
    try {
      logger.debug("Attempting to delete profile for DID:", this.activeDid);
      const success = await this.profileService.deleteProfile(this.activeDid);
      if (success) {
        this.notify.success(ACCOUNT_VIEW_CONSTANTS.SUCCESS.PROFILE_DELETED);
        this.userProfileDesc = "";
        this.userProfileLatitude = 0;
        this.userProfileLongitude = 0;
        this.includeUserProfileLocation = false;
        this.isMapReady = false; // Reset map state
        logger.debug("Profile deleted successfully, UI state reset");
      } else {
        this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR);
      }
    } catch (error) {
      logger.error("Error in deleteProfile component method:", error);

      // Show more specific error message if available
      if (error instanceof Error) {
        this.notify.error(error.message);
      } else {
        this.notify.error(ACCOUNT_VIEW_CONSTANTS.ERRORS.PROFILE_DELETE_ERROR);
      }
    }
  }

  private handleQRCodeClick() {
    if (Capacitor.isNativePlatform()) {
      this.$router.push({ name: "contact-qr-scan-full" });
    } else {
      this.$router.push({ name: "contact-qr" });
    }
  }

  onProfileMapClick(event: LeafletMouseEvent) {
    try {
      if (event && event.latlng) {
        this.userProfileLatitude = event.latlng.lat;
        this.userProfileLongitude = event.latlng.lng;
      }
    } catch (error) {
      logger.error("Error in onProfileMapClick:", error);
    }
  }

  onLocationCheckboxChange(): void {
    try {
      logger.debug(
        "Location checkbox changed, new value:",
        this.includeUserProfileLocation,
      );
      if (!this.includeUserProfileLocation) {
        // Location checkbox was unchecked, clean up map state
        this.isMapReady = false;
        this.userProfileLatitude = 0;
        this.userProfileLongitude = 0;
        logger.debug("Location unchecked, map state reset");
      } else {
        // Location checkbox was checked, start map initialization timeout
        this.isMapReady = false;
        logger.debug("Location checked, starting map initialization timeout");

        // Try to set map ready after a short delay to allow Vue to render
        setTimeout(() => {
          if (!this.isMapReady) {
            logger.debug("Setting map ready after timeout");
            this.isMapReady = true;
          }
        }, 1000); // 1 second delay

        this.handleMapInitFailure();
      }
    } catch (error) {
      logger.error("Error in onLocationCheckboxChange:", error);
    }
  }

  // IdentitySection event handlers
  onEditName() {
    const dialog = this.$refs.userNameDialog as UserNameDialogRef | undefined;
    if (dialog && typeof dialog.open === "function") {
      dialog.open((name?: string) => {
        if (name) this.givenName = name;
      });
    } else {
      this.notify.error("Name dialog not available.");
      logger.error(
        "UserNameDialog ref is missing or open() is not a function",
        dialog,
      );
    }
  }
  onShowQrCode() {
    this.handleQRCodeClick();
  }
  onAddImage() {
    this.openImageDialog();
  }
  onDeleteImage() {
    this.confirmDeleteImage();
  }
  onShowLargeIdenticonId(id: string) {
    this.showLargeIdenticonId = id;
  }
  onShowLargeIdenticonUrl(url: string) {
    this.showLargeIdenticonUrl = url;
  }
  onCloseLargeIdenticon() {
    this.showLargeIdenticonId = undefined;
    this.showLargeIdenticonUrl = undefined;
  }
  onCopyDid(did: string) {
    this.doCopyTwoSecRedo(did, () => (this.showDidCopy = !this.showDidCopy));
  }

  get showRegistrationNotice(): boolean {
    // Show the notice if not registered and any other conditions you want
    return !this.isRegistered;
  }

  onShareInfo() {
    // Navigate to QR code sharing page - mobile uses full scan, web uses basic
    if (Capacitor.isNativePlatform()) {
      this.$router.push({ name: "contact-qr-scan-full" });
    } else {
      this.$router.push({ name: "contact-qr" });
    }
  }

  onRecheckLimits() {
    this.checkLimits();
  }
}
</script>
