<template>
  <router-view />

  <!-- https://github.com/emmanuelsw/notiwind -->
  <NotificationGroup group="alert">
    <div
      class="fixed top-4 right-4 w-full max-w-sm flex flex-col items-start justify-end"
    >
      <Notification
        v-slot="{ notifications, close }"
        enter="transform ease-out duration-300 transition"
        enter-from="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-4"
        enter-to="translate-y-0 opacity-100 sm:translate-x-0"
        leave="transition ease-in duration-500"
        leave-from="opacity-100"
        leave-to="opacity-0"
        move="transition duration-500"
        move-delay="delay-300"
      >
        <div
          v-for="notification in notifications"
          :key="notification.id"
          class="w-full"
          role="alert"
        >
          <div
            v-if="notification.type === 'toast'"
            class="w-full max-w-sm mx-auto mb-3 overflow-hidden bg-slate-900/90 text-white rounded-lg shadow-md"
          >
            <div class="w-full px-4 py-3">
              <span class="font-semibold">{{ notification.title }}</span>
              <p class="text-sm">{{ notification.text }}</p>
            </div>
          </div>

          <div
            v-if="notification.type === 'info'"
            class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-slate-100 rounded-lg shadow-md"
          >
            <div
              class="flex items-center justify-center w-12 bg-slate-600 text-slate-100"
            >
              <fa icon="circle-info" class="fa-fw fa-xl"></fa>
            </div>

            <div class="relative w-full pl-4 pr-8 py-2 text-slate-900">
              <span class="font-semibold">{{ notification.title }}</span>
              <p class="text-sm">{{ notification.text }}</p>

              <button
                @click="close(notification.id)"
                class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-slate-200 text-slate-600"
              >
                <fa icon="xmark" class="fa-fw"></fa>
              </button>
            </div>
          </div>

          <div
            v-if="notification.type === 'success'"
            class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-emerald-100 rounded-lg shadow-md"
          >
            <div
              class="flex items-center justify-center w-12 bg-emerald-600 text-emerald-100"
            >
              <fa icon="circle-info" class="fa-fw fa-xl"></fa>
            </div>

            <div class="relative w-full pl-4 pr-8 py-2 text-emerald-900">
              <span class="font-semibold">{{ notification.title }}</span>
              <p class="text-sm">{{ notification.text }}</p>

              <button
                @click="close(notification.id)"
                class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-emerald-200 text-emerald-600"
              >
                <fa icon="xmark" class="fa-fw"></fa>
              </button>
            </div>
          </div>

          <div
            v-if="notification.type === 'warning'"
            class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-amber-100 rounded-lg shadow-md"
          >
            <div
              class="flex items-center justify-center w-12 bg-amber-600 text-amber-100"
            >
              <fa icon="triangle-exclamation" class="fa-fw fa-xl"></fa>
            </div>

            <div class="relative w-full pl-4 pr-8 py-2 text-amber-900">
              <span class="font-semibold">{{ notification.title }}</span>
              <p class="text-sm">{{ notification.text }}</p>

              <button
                @click="close(notification.id)"
                class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-amber-200 text-amber-600"
              >
                <fa icon="xmark" class="fa-fw"></fa>
              </button>
            </div>
          </div>

          <div
            v-if="notification.type === 'danger'"
            class="flex w-full max-w-sm mx-auto mb-3 overflow-hidden bg-rose-100 rounded-lg shadow-md"
          >
            <div
              class="flex items-center justify-center w-12 bg-rose-600 text-rose-100"
            >
              <fa icon="triangle-exclamation" class="fa-fw fa-xl"></fa>
            </div>

            <div class="relative w-full pl-4 pr-8 py-2 text-rose-900">
              <span class="font-semibold">{{ notification.title }}</span>
              <p class="text-sm">{{ notification.text }}</p>

              <button
                @click="close(notification.id)"
                class="absolute top-2 right-2 px-0.5 py-0 rounded-full bg-rose-200 text-rose-600"
              >
                <fa icon="xmark" class="fa-fw"></fa>
              </button>
            </div>
          </div>
        </div>
      </Notification>
    </div>
  </NotificationGroup>

  <NotificationGroup group="modal">
    <div class="fixed z-[100] top-0 inset-x-0 w-full">
      <Notification
        v-slot="{ notifications, close }"
        enter="transform ease-out duration-300 transition"
        enter-from="translate-y-2 opacity-0 sm:translate-y-4"
        enter-to="translate-y-0 opacity-100 sm:translate-y-0"
        leave="transition ease-in duration-500"
        leave-from="opacity-100"
        leave-to="opacity-0"
        move="transition duration-500"
        move-delay="delay-300"
      >
        <div
          v-for="notification in notifications"
          :key="notification.id"
          class="w-full"
          role="alert"
        >
          <div
            v-if="notification.type === 'notification-permission'"
            class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
          >
            <div
              class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
            >
              <div class="w-full px-6 py-6 text-slate-900 text-center">
                <p v-if="serviceWorkerReady" class="text-lg mb-4">
                  Would you like to <b>turn on</b> notifications for this app?
                </p>
                <p v-else class="text-lg mb-4">
                  Waiting for system initialization, which may take up to 10
                  seconds...
                  <fa icon="spinner" spin />
                </p>

                <button
                  v-if="serviceWorkerReady"
                  class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
                  @click="
                    close(notification.id);
                    turnOnNotifications();
                  "
                >
                  Turn on Notifications
                </button>

                <button
                  @click="close(notification.id)"
                  class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
                >
                  Maybe Later
                </button>
              </div>
            </div>
          </div>
          <div
            v-if="notification.type === 'notification-mute'"
            class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
          >
            <div
              class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
            >
              <div class="w-full px-6 py-6 text-slate-900 text-center">
                <p class="text-lg mb-4">Mute app notifications:</p>

                <button
                  class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
                >
                  For 1 Hour
                </button>
                <button
                  class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
                >
                  For 8 Hours
                </button>
                <button
                  class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
                >
                  For 24 Hours
                </button>
                <button
                  class="block w-full text-center text-md font-bold uppercase bg-blue-600 text-white px-2 py-2 rounded-md mb-2"
                >
                  Until I turn it back on
                </button>
                <button
                  @click="close(notification.id)"
                  class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
                >
                  Cancel
                </button>
              </div>
            </div>
          </div>
          <div
            v-if="notification.type === 'notification-off'"
            class="absolute inset-0 h-screen flex flex-col items-center justify-center bg-slate-900/50"
          >
            <div
              class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
            >
              <div class="w-full px-6 py-6 text-slate-900 text-center">
                <p class="text-lg mb-4">
                  Would you like to <b>turn off</b> notifications for this app?
                </p>

                <button
                  @click="
                    close(notification.id);
                    turnOffNotifications();
                  "
                  class="block w-full text-center text-md font-bold uppercase bg-rose-600 text-white px-2 py-2 rounded-md mb-2"
                >
                  Turn Off Notifications
                </button>
                <button
                  @click="close(notification.id)"
                  class="block w-full text-center text-md font-bold uppercase bg-slate-600 text-white px-2 py-2 rounded-md"
                >
                  Leave it On
                </button>
              </div>
            </div>
          </div>
        </div>
      </Notification>
    </div>
  </NotificationGroup>
</template>

<style></style>

<script lang="ts">
import { Vue, Component } from "vue-facing-decorator";
import axios from "axios";
interface ServiceWorkerMessage {
  type: string;
  data: string;
}

interface ServiceWorkerResponse {
  // Define the properties and their types
  success: boolean;
  message?: string;
}

// Example interface for error
interface ErrorResponse {
  message: string;
  // Other properties as needed
}

interface VapidResponse {
  data: {
    vapidKey: string;
  };
}

import { DEFAULT_PUSH_SERVER, NotificationIface } from "@/constants/app";
import { db } from "@/db/index";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import { sendTestThroughPushServer } from "@/libs/util";

@Component
export default class App extends Vue {
  $notify!: (notification: NotificationIface, timeout?: number) => void;

  b64 = "";
  serviceWorkerReady = false;

  async mounted() {
    try {
      await db.open();
      const settings = await db.settings.get(MASTER_SETTINGS_KEY);
      let pushUrl = DEFAULT_PUSH_SERVER;
      if (settings?.webPushServer) {
        pushUrl = settings.webPushServer;
      }

      await axios
        .get(pushUrl + "/web-push/vapid")
        .then((response: VapidResponse) => {
          this.b64 = response.data?.vapidKey || "";
          console.log("Got vapid key:", this.b64);
          navigator.serviceWorker.addEventListener("controllerchange", () => {
            console.log("New service worker is now controlling the page");
          });
        });
      if (!this.b64) {
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Error Setting Notifications",
            text: "Could not set notifications.",
          },
          -1,
        );
      }
    } catch (error) {
      if (window.location.host.startsWith("localhost")) {
        console.log("Ignoring the error getting VAPID for local development.");
      } else {
        console.error("Got an error initializing notifications:", error);
        this.$notify(
          {
            group: "alert",
            type: "danger",
            title: "Error Setting Notifications",
            text: "Got an error setting notifications.",
          },
          -1,
        );
      }
    }
    // there may be a long pause here on first initialization
    navigator.serviceWorker?.ready.then(() => {
      this.serviceWorkerReady = true;
    });
  }

  private sendMessageToServiceWorker(
    message: ServiceWorkerMessage,
  ): Promise<unknown> {
    return new Promise((resolve, reject) => {
      if (navigator.serviceWorker.controller) {
        const messageChannel = new MessageChannel();

        messageChannel.port1.onmessage = (event: MessageEvent) => {
          if (event.data.error) {
            reject(event.data.error as ErrorResponse);
          } else {
            resolve(event.data as ServiceWorkerResponse);
          }
        };

        navigator.serviceWorker.controller.postMessage(message, [
          messageChannel.port2,
        ]);
      } else {
        reject("Service worker controller not available");
      }
    });
  }

  private askPermission(): Promise<NotificationPermission> {
    console.log("Requesting permission for notifications:", navigator);
    if (!("serviceWorker" in navigator && navigator.serviceWorker.controller)) {
      return Promise.reject("Service worker not available.");
    }

    const secret = localStorage.getItem("secret");
    if (!secret) {
      return Promise.reject("No secret found.");
    }

    return this.sendSecretToServiceWorker(secret)
      .then(() => this.checkNotificationSupport())
      .then(() => this.requestNotificationPermission())
      .catch((error) => Promise.reject(error));
  }

  private sendSecretToServiceWorker(secret: string): Promise<void> {
    const message: ServiceWorkerMessage = {
      type: "SEND_LOCAL_DATA",
      data: secret,
    };

    return this.sendMessageToServiceWorker(message).then((response) => {
      console.log("Response from service worker:", response);
    });
  }

  private checkNotificationSupport(): Promise<void> {
    if (!("Notification" in window)) {
      alert("This browser does not support notifications.");
      return Promise.reject("This browser does not support notifications.");
    }
    if (Notification.permission === "granted") {
      return Promise.resolve();
    }
    return Promise.resolve();
  }

  private requestNotificationPermission(): Promise<NotificationPermission> {
    return Notification.requestPermission().then((permission) => {
      if (permission !== "granted") {
        alert(
          "Allow this app permission to make notifications for personal reminders." +
            " You can adjust them at any time in your settings.",
        );
        throw new Error("We weren't granted permission.");
      }
      return permission;
    });
  }

  public async turnOnNotifications() {
    return this.askPermission()
      .then((permission) => {
        console.log("Permission granted:", permission);

        // Call the function and handle promises
        this.subscribeToPush()
          .then(() => {
            console.log("Subscribed successfully.");
            return navigator.serviceWorker?.ready;
          })
          .then((registration) => {
            return registration.pushManager.getSubscription();
          })
          .then(async (subscription) => {
            if (subscription) {
              await this.$notify(
                {
                  group: "alert",
                  type: "info",
                  title: "Notification Setup Underway",
                  text: "Setting up notifications for interesting activity, which takes about 10 seconds. If you don't see a final confirmation, check the 'Troubleshoot' page.",
                },
                -1,
              );
              this.sendSubscriptionToServer(subscription);
              return subscription;
            } else {
              throw new Error("Subscription object is not available.");
            }
          })
          .then(async (subscription) => {
            console.log(
              "Subscription data sent to server and all finished successfully.",
            );
            await sendTestThroughPushServer(subscription, true);
            this.$notify(
              {
                group: "alert",
                type: "success",
                title: "Notifications Turned On",
                text: "Notifications are on. You should see at least one on your device; if not, check the 'Troubleshoot' page.",
              },
              -1,
            );
          })
          .catch((error) => {
            console.error(
              "Subscription or server communication failed:",
              error,
            );
            alert(
              "Subscription or server communication failed. Try again in a while.",
            );
          });
      })
      .catch((error) => {
        console.error(
          "An error occurred setting notification permissions:",
          error,
        );
        alert("Some error occurred setting notification permissions.");
      });
  }

  private urlBase64ToUint8Array(base64String: string): Uint8Array {
    const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
    const base64 = (base64String + padding)
      .replace(/-/g, "+")
      .replace(/_/g, "/");
    const rawData = window.atob(base64);
    const outputArray = new Uint8Array(rawData.length);

    for (let i = 0; i < rawData.length; ++i) {
      outputArray[i] = rawData.charCodeAt(i);
    }
    return outputArray;
  }

  private subscribeToPush(): Promise<void> {
    return new Promise<void>((resolve, reject) => {
      if (!("serviceWorker" in navigator && "PushManager" in window)) {
        const errorMsg = "Push messaging is not supported";
        console.warn(errorMsg);
        return reject(new Error(errorMsg));
      }

      if (Notification.permission !== "granted") {
        const errorMsg = "Notification permission not granted";
        console.warn(errorMsg);
        return reject(new Error(errorMsg));
      }

      const applicationServerKey = this.urlBase64ToUint8Array(this.b64);
      const options: PushSubscriptionOptions = {
        userVisibleOnly: true,
        applicationServerKey: applicationServerKey,
      };

      navigator.serviceWorker.ready
        .then((registration) => {
          return registration.pushManager.subscribe(options);
        })
        .then((subscription) => {
          console.log("Push subscription successful:", subscription);
          resolve();
        })
        .catch((error) => {
          console.error("Push subscription failed:", error, options);

          // Inform the user about the issue
          alert(
            "We encountered an issue setting up push notifications. " +
              "If you wish to revoke notification permissions, please do so in your browser settings.",
          );

          reject(error);
        });
    });
  }

  private sendSubscriptionToServer(
    subscription: PushSubscription,
  ): Promise<void> {
    console.log("About to send subscription...", subscription);
    return fetch("/web-push/subscribe", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(subscription),
    }).then((response) => {
      if (!response.ok) {
        throw new Error("Failed to send subscription to server");
      }
      console.log("Subscription sent to server successfully.");
    });
  }

  async turnOffNotifications() {
    let subscription;
    const pushProviderSuccess = await navigator.serviceWorker?.ready
      .then((registration) => {
        return registration.pushManager.getSubscription();
      })
      .then((subscript) => {
        subscription = subscript;
        if (subscription) {
          return subscription.unsubscribe();
        } else {
          console.log("Subscription object is not available.");
          return false;
        }
      })
      .catch((error) => {
        console.error("Push provider server communication failed:", error);
        return false;
      });

    const pushServerSuccess = await fetch("/web-push/unsubscribe", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(subscription),
    })
      .then((response) => {
        return response.ok;
      })
      .catch((error) => {
        console.error("Push server communication failed:", error);
        return false;
      });

    alert(
      "Notifications are off. Push provider unsubscribe " +
        (pushProviderSuccess ? "succeeded" : "failed") +
        (pushProviderSuccess === pushServerSuccess ? " and" : " but") +
        " push server unsubscribe " +
        (pushServerSuccess ? "succeeded" : "failed") +
        ".",
    );
  }
}
</script>