/** * Data Export Section Component * * Provides UI and functionality for
exporting user data and backing up identifier seeds. * Includes buttons for seed
backup and database export, with platform-specific download instructions. * *
Features: * - Platform-specific export handling (web vs. native) * - Proper
resource cleanup for blob URLs * - Robust error handling with user-friendly
messages * - Conditional UI based on platform capabilities * * @component *
@displayName DataExportSection * @example * ```vue *
<DataExportSection :active-did="currentDid" />
* ``` * * @author Matthew Raymer * @since 2025-01-25 * @version 1.1.0 */

<template>
  <div
    id="sectionDataExport"
    class="bg-slate-100 rounded-md overflow-hidden px-4 py-4 mt-8 mb-8"
  >
    <div class="mb-2 font-bold">Data Export</div>
    <router-link
      v-if="activeDid"
      :to="{ name: 'seed-backup' }"
      class="block w-full 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-2 mt-2"
    >
      Backup Identifier Seed
    </router-link>

    <button
      :class="{ hidden: isDownloadInProgress }"
      class="block w-full 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"
      @click="exportDatabase()"
    >
      Download Contacts
    </button>

    <!-- Hidden download link for web platform - always rendered for ref access -->
    <a
      v-if="isWebPlatform"
      ref="downloadLink"
      :href="downloadUrl"
      :download="fileName"
      :class="{ hidden: !downloadUrl }"
      class="block w-full text-center text-md bg-gradient-to-b from-green-500 to-green-800 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
    >
      If no download happened yet, click again here to download now.
    </a>
    <div v-if="capabilities.needsFileHandlingInstructions" class="mt-4">
      <p>
        After the download, you can save the file in your preferred storage
        location.
      </p>
      <ul>
        <li v-if="capabilities.isIOS" class="list-disc list-outside ml-4">
          On iOS: You will be prompted to choose a location to save your backup
          file.
        </li>
        <li
          v-if="capabilities.isMobile && !capabilities.isIOS"
          class="list-disc list-outside ml-4"
        >
          On Android: You will be prompted to choose a location to save your
          backup file.
        </li>
      </ul>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from "vue-facing-decorator";
import { AppString, NotificationIface } from "../constants/app";

import { logger } from "../utils/logger";
import { contactsToExportJson } from "../libs/util";
import { createNotifyHelpers } from "@/utils/notify";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";

/**
 * @vue-component
 * Data Export Section Component
 * Handles database export and seed backup functionality with platform-specific behavior
 */
@Component({
  mixins: [PlatformServiceMixin],
})
export default class DataExportSection extends Vue {
  /**
   * Notification function injected by Vue
   * Used to show success/error messages to the user
   */
  $notify!: (notification: NotificationIface, timeout?: number) => void;

  /**
   * Active DID (Decentralized Identifier) of the user
   * Controls visibility of seed backup option
   * @required
   */
  @Prop({ required: true }) readonly activeDid!: string;

  /**
   * URL for the database export download
   * Created and revoked dynamically during export process
   * Only used in web platform
   */
  downloadUrl = "";

  /**
   * Notification helper for consistent notification patterns
   * Created as a getter to ensure $notify is available when called
   */
  get notify() {
    return createNotifyHelpers(this.$notify);
  }

  /**
   * NOTE: PlatformServiceMixin provides both concise helpers (e.g. $contacts, capabilities)
   * and the full platformService instance for advanced/native features (e.g. writeAndShareFile).
   * Always use 'this.platformService' as a property (never as a function).
   */
  declare readonly platformService: import("@/services/PlatformService").PlatformService;

  /**
   * Computed property to check if we're on web platform
   */
  private get isWebPlatform(): boolean {
    return this.capabilities.hasFileDownload;
  }

  /**
   * Computed property to check if download is in progress
   */
  private get isDownloadInProgress(): boolean {
    return Boolean(this.downloadUrl && this.isWebPlatform);
  }

  /**
   * Computed property for the export file name
   */
  private get fileName(): string {
    return `${AppString.APP_NAME_NO_SPACES}-backup-contacts.json`;
  }

  /**
   * Lifecycle hook to clean up resources
   * Revokes object URL when component is unmounted (web platform only)
   */
  beforeUnmount() {
    if (this.downloadUrl && this.isWebPlatform) {
      URL.revokeObjectURL(this.downloadUrl);
      this.downloadUrl = "";
    }
  }

  /**
   * Exports the database to a JSON file
   * Uses platform-specific methods for saving the exported data
   * Shows success/error notifications to user
   *
   * @throws {Error} If export fails
   */
  public async exportDatabase(): Promise<void> {
    try {
      // Fetch contacts from database using mixin's cached method
      const allContacts = await this.$contacts();

      // Convert contacts to export format
      const exportData = contactsToExportJson(allContacts);
      const jsonStr = JSON.stringify(exportData, null, 2);
      const blob = new Blob([jsonStr], { type: "application/json" });

      // Handle export based on platform capabilities
      if (this.isWebPlatform) {
        await this.handleWebExport(blob);
      } else if (this.capabilities.hasFileSystem) {
        await this.handleNativeExport(jsonStr);
      } else {
        throw new Error("This platform does not support file downloads.");
      }

      this.notify.success(
        this.isWebPlatform
          ? "See your downloads directory for the backup."
          : "The backup file has been saved.",
      );
    } catch (error) {
      logger.error("Export Error:", error);
      this.notify.error(
        `There was an error exporting the data: ${error instanceof Error ? error.message : "Unknown error"}`,
      );
    }
  }

  /**
   * Handles export for web platform using download link
   * @param blob The blob to download
   */
  private async handleWebExport(blob: Blob): Promise<void> {
    this.downloadUrl = URL.createObjectURL(blob);

    try {
      // Wait for next tick to ensure DOM is updated
      await this.$nextTick();

      const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement;
      if (!downloadAnchor) {
        throw new Error("Download link element not found. Please try again.");
      }

      downloadAnchor.click();

      // Clean up the URL after a delay
      setTimeout(() => {
        URL.revokeObjectURL(this.downloadUrl);
        this.downloadUrl = "";
      }, 1000);
    } catch (error) {
      // Clean up the URL on error
      if (this.downloadUrl) {
        URL.revokeObjectURL(this.downloadUrl);
        this.downloadUrl = "";
      }
      throw error;
    }
  }

  /**
   * Handles export for native platforms using file system
   * @param jsonStr The JSON string to save
   */
  private async handleNativeExport(jsonStr: string): Promise<void> {
    await this.platformService.writeAndShareFile(this.fileName, jsonStr);
  }
}
</script>
