<template>
  <div v-if="visible" class="dialog-overlay z-[60]">
    <div class="dialog relative">
      <div class="text-lg text-center font-bold relative">
        <h1 id="ViewHeading" class="text-center font-bold">
          <span v-if="uploading">Uploading Image&hellip;</span>
          <span v-else-if="blob">{{
            crop ? "Crop Image" : "Preview Image"
          }}</span>
          <span v-else-if="showCameraPreview">Upload Image</span>
          <span v-else>Add Photo</span>
        </h1>
        <div
          class="text-2xl text-center px-1 py-0.5 leading-none absolute -right-1 top-0"
          @click="close()"
        >
          <font-awesome icon="xmark" class="w-[1em]"></font-awesome>
        </div>
      </div>

      <!-- FEEDBACK: Show if camera preview is not visible after mounting -->
      <div
        v-if="!showCameraPreview && !blob && isRegistered"
        class="bg-red-100 text-red-700 border border-red-400 rounded px-4 py-3 my-4 text-sm"
      >
        <strong>Camera preview not started.</strong>
        <div v-if="cameraState === 'off'">
          <span v-if="platformCapabilities.isMobile">
            <b>Note:</b> This mobile browser may not support direct camera
            access, or the app is treating it as a native app.<br />
            <b>Tip:</b> Try using a desktop browser, or check if your browser
            supports camera access for web apps.<br />
            <b>Developer:</b> The platform detection logic may be skipping
            camera preview for mobile browsers. <br />
            <b>Action:</b> Review <code>platformCapabilities.isMobile</code> and
            ensure web browsers on mobile are not treated as native apps.
          </span>
          <span v-else>
            <b>Tip:</b> Your browser supports camera APIs, but the preview did
            not start. Try refreshing the page or checking browser permissions.
          </span>
        </div>
        <div v-else-if="cameraState === 'error'">
          <b>Error:</b> {{ error || cameraStateMessage }}
        </div>
        <div v-else>
          <b>Status:</b> {{ cameraStateMessage || "Unknown reason." }}
        </div>
      </div>

      <div class="mt-4">
        <template v-if="isRegistered">
          <div v-if="!blob">
            <div
              class="border-b border-dashed border-slate-300 text-orange-400 mb-4 font-bold text-sm"
            >
              <span class="block w-fit mx-auto -mb-2.5 bg-white px-2">
                Take a photo with your camera
              </span>
            </div>
            <div
              v-if="showCameraPreview"
              class="camera-preview relative flex bg-black overflow-hidden mb-4"
            >
              <!-- Diagnostic Panel -->
              <div
                v-if="showDiagnostics"
                class="absolute top-0 left-0 right-0 bg-black/80 text-white text-xs p-2 pt-8 z-20 overflow-auto max-h-[50vh]"
              >
                <div class="grid grid-cols-2 gap-2">
                  <div>
                    <p><strong>Camera State:</strong> {{ cameraState }}</p>
                    <p>
                      <strong>State Message:</strong>
                      {{ cameraStateMessage || "None" }}
                    </p>
                    <p><strong>Error:</strong> {{ error || "None" }}</p>
                    <p>
                      <strong>Preview Active:</strong>
                      {{ showCameraPreview ? "Yes" : "No" }}
                    </p>
                    <p>
                      <strong>Stream Active:</strong>
                      {{ !!cameraStream ? "Yes" : "No" }}
                    </p>
                  </div>
                  <div>
                    <p><strong>Browser:</strong> {{ userAgent }}</p>
                    <p>
                      <strong>HTTPS:</strong>
                      {{ isSecureContext ? "Yes" : "No" }}
                    </p>
                    <p>
                      <strong>MediaDevices:</strong>
                      {{ hasMediaDevices ? "Yes" : "No" }}
                    </p>
                    <p>
                      <strong>GetUserMedia:</strong>
                      {{ hasGetUserMedia ? "Yes" : "No" }}
                    </p>
                    <p>
                      <strong>Platform:</strong>
                      {{ platformCapabilities.isMobile ? "Mobile" : "Desktop" }}
                    </p>
                  </div>
                </div>
              </div>

              <!-- Toggle Diagnostics Button -->
              <button
                class="absolute top-2 right-2 bg-black/50 text-white px-2 py-1 rounded text-xs z-30"
                @click="toggleDiagnostics"
              >
                {{ showDiagnostics ? "Hide Diagnostics" : "Show Diagnostics" }}
              </button>
              <div class="camera-container w-full h-full relative">
                <video
                  ref="videoElement"
                  class="camera-video w-full h-full object-cover"
                  autoplay
                  playsinline
                  muted
                ></video>
                <div
                  class="absolute bottom-4 inset-x-0 flex items-center justify-center gap-4"
                >
                  <button
                    class="bg-white text-slate-800 p-3 rounded-full text-2xl leading-none"
                    @click="capturePhoto"
                  >
                    <font-awesome icon="camera" class="w-[1em]" />
                  </button>
                  <button
                    v-if="platformCapabilities.isMobile"
                    class="bg-white text-slate-800 p-3 rounded-full text-2xl leading-none"
                    @click="rotateCamera"
                  >
                    <font-awesome icon="rotate" class="w-[1em]" />
                  </button>
                </div>
              </div>
            </div>
            <div
              class="border-b border-dashed border-slate-300 text-orange-400 mt-4 mb-4 font-bold text-sm"
            >
              <span class="block w-fit mx-auto -mb-2.5 bg-white px-2">
                OR choose a file from your device
              </span>
            </div>
            <div class="mt-4">
              <input
                type="file"
                class="w-full file:text-center file:bg-gradient-to-b file:from-slate-400 file:to-slate-700 file:shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] file:text-white file:px-3 file:py-2 file:rounded-md file:border-none file:cursor-pointer file:me-2"
                @change="uploadImageFile"
              />
            </div>
            <div
              class="border-b border-dashed border-slate-300 text-orange-400 mt-4 mb-4 font-bold text-sm"
            >
              <span class="block w-fit mx-auto -mb-2.5 bg-white px-2">
                OR paste an image URL
              </span>
            </div>
            <div class="flex items-center gap-2 mt-4">
              <input
                v-model="imageUrl"
                type="text"
                class="block w-full rounded border border-slate-400 px-4 py-2"
                placeholder="https://example.com/image.jpg"
              />
              <button
                v-if="imageUrl"
                class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-3 py-2 rounded-md cursor-pointer"
                @click="acceptUrl"
              >
                <font-awesome icon="check" class="fa-fw" />
              </button>
            </div>
          </div>

          <div v-else>
            <div v-if="uploading" class="flex justify-center">
              <font-awesome
                icon="spinner"
                class="fa-spin fa-3x text-center block px-12 py-12"
              />
            </div>
            <div v-else>
              <div v-if="crop">
                <VuePictureCropper
                  :box-style="{
                    backgroundColor: '#f8f8f8',
                    margin: 'auto',
                  }"
                  :img="createBlobURL(blob)"
                  :options="{
                    viewMode: 1,
                    dragMode: 'crop',
                    aspectRatio: 1 / 1,
                  }"
                  class="max-h-[50vh] max-w-[90vw] object-contain"
                />
              </div>
              <div v-else>
                <div class="flex justify-center">
                  <img
                    :src="createBlobURL(blob)"
                    class="mt-2 rounded max-h-[50vh] max-w-[90vw] object-contain"
                  />
                </div>
              </div>
              <div
                :class="[
                  'grid gap-2 mt-2',
                  showRetry ? 'grid-cols-2' : 'grid-cols-1',
                ]"
              >
                <button
                  class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white py-2 px-3 rounded-md"
                  @click="uploadImage"
                >
                  <span>Upload</span>
                </button>
                <button
                  v-if="showRetry"
                  class="bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white py-2 px-3 rounded-md"
                  @click="retryImage"
                >
                  <span>Retry</span>
                </button>
              </div>
            </div>
          </div>
        </template>
        <template v-else>
          <div
            id="noticeBeforeUpload"
            class="bg-amber-200 text-amber-900 border-amber-500 border-dashed border text-center rounded-md overflow-hidden px-4 py-3"
            role="alert"
            aria-live="polite"
          >
            <p class="mb-2">
              Before you can upload a photo, a friend needs to register you.
            </p>
            <button
              class="inline-block text-md uppercase bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
              @click="handleQRCodeClick"
            >
              Share Your Info
            </button>
          </div>
        </template>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import axios from "axios";
import { ref } from "vue";
import { Component, Vue } from "vue-facing-decorator";
import VuePictureCropper, { cropper } from "vue-picture-cropper";
import { Capacitor } from "@capacitor/core";
import {
  DEFAULT_IMAGE_API_SERVER,
  NotificationIface,
  USE_DEXIE_DB,
} from "../constants/app";
import { retrieveSettingsForActiveAccount } from "../db/index";
import { accessToken } from "../libs/crypto";
import { logger } from "../utils/logger";
import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
import * as databaseUtil from "../db/databaseUtil";

const inputImageFileNameRef = ref<Blob>();

@Component({
  components: { VuePictureCropper },
  props: {
    isRegistered: {
      type: Boolean,
      default: true,
    },
    defaultCameraMode: {
      type: String,
      default: "environment",
      validator: (value: string) => ["environment", "user"].includes(value),
    },
  },
})
export default class ImageMethodDialog extends Vue {
  $notify!: (notification: NotificationIface, timeout?: number) => void;

  /** Active DID for user authentication */
  activeDid = "";

  /** Current image blob being processed */
  blob?: Blob;

  /** Type of claim for the image */
  claimType: string = "";

  /** Whether to show cropping interface */
  crop: boolean = false;

  /** Name of the selected file */
  fileName?: string;

  /** Callback function to set image URL after upload */
  imageCallback: (imageUrl?: string) => void = () => {};

  /** URL for image input */
  imageUrl?: string;

  /** Whether to show retry button */
  showRetry = true;

  /** Upload progress state */
  uploading = false;

  /** Dialog visibility state */
  visible = false;

  /** Whether to show camera preview */
  showCameraPreview = false;

  /** Camera stream reference */
  private cameraStream: MediaStream | null = null;

  /** Current camera facing mode */
  private currentFacingMode: "environment" | "user" = "environment";

  private platformService = PlatformServiceFactory.getInstance();
  URL = window.URL || window.webkitURL;

  private platformCapabilities = this.platformService.getCapabilities();

  // Add diagnostic properties
  showDiagnostics = false;
  userAgent = navigator.userAgent;
  isSecureContext = window.isSecureContext;
  hasMediaDevices = !!navigator.mediaDevices;
  hasGetUserMedia = !!(
    navigator.mediaDevices && navigator.mediaDevices.getUserMedia
  );
  cameraState:
    | "off"
    | "initializing"
    | "ready"
    | "active"
    | "error"
    | "permission_denied"
    | "not_found"
    | "in_use" = "off";
  cameraStateMessage?: string;
  error: string | null = null;

  /**
   * Lifecycle hook: Initializes component and retrieves user settings
   * @throws {Error} When settings retrieval fails
   */
  async mounted() {
    try {
      let settings = await databaseUtil.retrieveSettingsForActiveAccount();
      if (USE_DEXIE_DB) {
        settings = await retrieveSettingsForActiveAccount();
      }
      this.activeDid = settings.activeDid || "";
    } catch (error: unknown) {
      logger.error("Error retrieving settings from database:", error);
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text:
            error instanceof Error
              ? error.message
              : "There was an error retrieving your settings.",
        },
        -1,
      );
    }
  }

  /**
   * Lifecycle hook: Cleans up camera stream when component is destroyed
   */
  beforeDestroy() {
    this.stopCameraPreview();
  }

  open(setImageFn: (arg: string) => void, claimType: string, crop?: boolean) {
    logger.debug("ImageMethodDialog.open called");
    this.claimType = claimType;
    this.crop = !!crop;
    this.imageCallback = setImageFn;
    this.visible = true;
    this.currentFacingMode = this.defaultCameraMode as "environment" | "user";

    // Start camera preview immediately
    logger.debug("Starting camera preview from open()");
    this.startCameraPreview();
  }

  async uploadImageFile(event: Event) {
    const target = event.target as HTMLInputElement;
    if (!target.files) return;

    inputImageFileNameRef.value = target.files[0];
    const file = inputImageFileNameRef.value;
    if (file != null) {
      const reader = new FileReader();
      reader.onload = async (e) => {
        const data = e.target?.result as ArrayBuffer;
        if (data) {
          const blob = new Blob([new Uint8Array(data)], {
            type: file.type,
          });
          this.blob = blob;
          this.fileName = file.name;
          this.showRetry = false;
        }
      };
      reader.readAsArrayBuffer(file as Blob);
    }
  }

  async acceptUrl() {
    if (this.crop) {
      try {
        const urlBlobResponse = await axios.get(this.imageUrl as string, {
          responseType: "blob",
        });
        const fullUrl = new URL(this.imageUrl as string);
        const fileName = fullUrl.pathname.split("/").pop() as string;
        this.blob = urlBlobResponse.data as Blob;
        this.fileName = fileName;
        this.showRetry = false;
      } catch (error) {
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Error",
            text: "There was an error retrieving that image.",
          },
          5000,
        );
      }
    } else {
      this.imageCallback(this.imageUrl);
      this.close();
    }
  }

  close() {
    this.visible = false;
    this.stopCameraPreview();
    const bottomNav = document.querySelector("#QuickNav") as HTMLElement;
    if (bottomNav) {
      bottomNav.style.display = "";
    }
    this.blob = undefined;
    this.showCameraPreview = false;
  }

  async startCameraPreview() {
    logger.debug("startCameraPreview called");
    logger.debug("Current showCameraPreview state:", this.showCameraPreview);
    logger.debug("Platform capabilities:", this.platformCapabilities);
    logger.debug("MediaDevices available:", !!navigator.mediaDevices);
    logger.debug(
      "getUserMedia available:",
      !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia),
    );

    try {
      this.cameraState = "initializing";
      this.cameraStateMessage = "Requesting camera access...";
      this.showCameraPreview = true;
      await this.$nextTick();

      if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
        throw new Error("Camera API not available in this browser");
      }

      const stream = await navigator.mediaDevices.getUserMedia({
        video: { facingMode: this.currentFacingMode },
      });
      logger.debug("Camera access granted");
      this.cameraStream = stream;
      this.cameraState = "active";
      this.cameraStateMessage = "Camera is active";

      await this.$nextTick();

      const videoElement = this.$refs.videoElement as HTMLVideoElement;
      if (videoElement) {
        videoElement.srcObject = stream;
        await new Promise((resolve) => {
          videoElement.onloadedmetadata = () => {
            videoElement
              .play()
              .then(() => {
                logger.debug("Video element started playing");
                resolve(true);
              })
              .catch((error) => {
                logger.error("Error playing video:", error);
                throw error;
              });
          };
        });
      } else {
        logger.error("Video element not found");
        throw new Error("Video element not found");
      }
    } catch (error) {
      logger.error("Error starting camera preview:", error);
      let errorMessage =
        error instanceof Error ? error.message : "Failed to access camera";
      if (
        error instanceof Error &&
        (error.name === "NotReadableError" || error.name === "TrackStartError")
      ) {
        errorMessage =
          "Camera is in use by another application. Please close any other apps or browser tabs using the camera and try again.";
      } else if (
        error instanceof Error &&
        (error.name === "NotAllowedError" ||
          error.name === "PermissionDeniedError")
      ) {
        errorMessage =
          "Camera access was denied. Please allow camera access in your browser settings.";
      }
      this.cameraState = "error";
      this.cameraStateMessage = errorMessage;
      this.error = errorMessage;
      this.showCameraPreview = false;
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text: errorMessage,
        },
        5000,
      );
    }
  }

  stopCameraPreview() {
    if (this.cameraStream) {
      this.cameraStream.getTracks().forEach((track) => track.stop());
      this.cameraStream = null;
    }
    this.showCameraPreview = false;
    this.cameraState = "off";
    this.cameraStateMessage = "Camera stopped";
    this.error = null;
  }

  async capturePhoto() {
    if (!this.cameraStream) return;

    try {
      const videoElement = this.$refs.videoElement as HTMLVideoElement;
      const canvas = document.createElement("canvas");
      canvas.width = videoElement.videoWidth;
      canvas.height = videoElement.videoHeight;
      const ctx = canvas.getContext("2d");
      ctx?.drawImage(videoElement, 0, 0, canvas.width, canvas.height);

      canvas.toBlob(
        (blob) => {
          if (blob) {
            this.blob = blob;
            this.fileName = `photo_${Date.now()}.jpg`;
            this.showRetry = true;
            this.stopCameraPreview();
          }
        },
        "image/jpeg",
        0.95,
      );
    } catch (error) {
      logger.error("Error capturing photo:", error);
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text: "Failed to capture photo. Please try again.",
        },
        5000,
      );
    }
  }

  async rotateCamera() {
    // Toggle between front and back cameras
    this.currentFacingMode =
      this.currentFacingMode === "environment" ? "user" : "environment";

    // Stop current stream
    if (this.cameraStream) {
      this.cameraStream.getTracks().forEach((track) => track.stop());
      this.cameraStream = null;
    }

    // Start new stream with updated facing mode
    await this.startCameraPreview();
  }

  private createBlobURL(blob: Blob): string {
    return URL.createObjectURL(blob);
  }

  async retryImage() {
    this.blob = undefined;
    if (!this.platformCapabilities.isNativeApp) {
      await this.startCameraPreview();
    }
  }

  async uploadImage() {
    this.uploading = true;

    if (this.crop) {
      this.blob = (await cropper?.getBlob()) || undefined;
    }

    const token = await accessToken(this.activeDid);
    const headers = {
      Authorization: "Bearer " + token,
    };
    const formData = new FormData();
    if (!this.blob) {
      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text: "There was an error finding the picture. Please try again.",
        },
        5000,
      );
      this.uploading = false;
      this.close();
      return;
    }
    formData.append("image", this.blob, this.fileName || "photo.jpg");
    formData.append("claimType", this.claimType);
    try {
      if (
        window.location.hostname === "localhost" &&
        !DEFAULT_IMAGE_API_SERVER.includes("localhost")
      ) {
        logger.log(
          "Using shared image API server, so only users on that server can play with images.",
        );
      }
      const response = await axios.post(
        DEFAULT_IMAGE_API_SERVER + "/image",
        formData,
        { headers },
      );
      this.uploading = false;

      this.close();
      this.imageCallback(response.data.url as string);
    } catch (error: unknown) {
      let errorMessage = "There was an error saving the picture.";

      if (axios.isAxiosError(error)) {
        const status = error.response?.status;
        const data = error.response?.data;

        if (status === 401) {
          errorMessage = "Authentication failed. Please try logging in again.";
        } else if (status === 413) {
          errorMessage = "Image file is too large. Please try a smaller image.";
        } else if (status === 415) {
          errorMessage =
            "Unsupported image format. Please try a different image.";
        } else if (status && status >= 500) {
          errorMessage = "Server error. Please try again later.";
        } else if (data?.message) {
          errorMessage = data.message;
        }
      }

      this.$notify(
        {
          group: "alert",
          type: "danger",
          title: "Error",
          text: errorMessage,
        },
        5000,
      );
      this.uploading = false;
      this.blob = undefined;
      this.close();
    }
  }

  // Add toggle method
  toggleDiagnostics() {
    this.showDiagnostics = !this.showDiagnostics;
  }

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

<style>
.dialog-overlay {
  z-index: 50;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 1.5rem;
}

.dialog {
  background-color: white;
  padding: 1rem;
  border-radius: 0.5rem;
  width: 100%;
  max-width: 700px;
  max-height: 90vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* Add styles for diagnostic panel */
.diagnostic-panel {
  font-family: monospace;
  white-space: pre-wrap;
  word-break: break-all;
}
</style>
