<template>
  <div>
    <div class="sm:flex sm:items-center">
      <div class="sm:flex-auto">
        <h1 class="text-base font-semibold text-zinc-100">
          {{ $t("library.admin.sources.sources") }}
        </h1>
        <p class="mt-2 text-sm text-zinc-400">
          {{ $t("library.admin.sources.desc") }}
        </p>
      </div>
      <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
        <button
          class="block rounded-md bg-blue-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
          @click="() => (actionSourceOpen = true)"
        >
          {{ $t("common.create") }}
        </button>
      </div>
    </div>
    <div class="mt-4 flow-root">
      <SourceTable
        :sources="sources"
        :edit-source="edit"
        :delete-source="deleteSource"
      />
    </div>

    <ModalTemplate v-model="actionSourceOpen">
      <template #default>
        <div>
          <DialogTitle
            v-if="createMode"
            as="h3"
            class="text-lg font-medium leading-6 text-white"
          >
            {{ $t("library.admin.sources.create") }}
          </DialogTitle>
          <DialogTitle
            v-else
            as="h3"
            class="text-lg font-medium leading-6 text-white"
          >
            {{ $t("library.admin.sources.edit") }}
          </DialogTitle>
          <p class="mt-1 text-zinc-400 text-sm">
            {{ $t("library.admin.sources.createDesc") }}
          </p>
        </div>
        <form
          class="mt-2 space-y-4"
          @submit.prevent="() => performActionSource_wrapper()"
        >
          <div>
            <label
              for="name"
              class="block text-sm font-medium leading-6 text-zinc-100"
              >{{ $t("common.name") }}</label
            >
            <p class="text-zinc-400 block text-xs font-medium leading-6">
              {{ $t("library.admin.sources.nameDesc") }}
            </p>
            <div class="mt-2">
              <input
                id="name"
                v-model="sourceName"
                name="name"
                type="text"
                autocomplete="name"
                :placeholder="$t('library.admin.sources.namePlaceholder')"
                class="block w-full rounded-md border-0 py-1.5 px-3 bg-zinc-800 disabled:bg-zinc-900/80 text-zinc-100 disabled:text-zinc-400 shadow-sm ring-1 ring-inset ring-zinc-700 disabled:ring-zinc-800 placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
              />
            </div>
          </div>
          <div v-if="createMode">
            <label class="block text-sm font-medium leading-6 text-zinc-100">{{
              $t("type")
            }}</label>
            <p class="text-zinc-400 block text-xs font-medium leading-6">
              {{ $t("library.admin.sources.typeDesc") }}
            </p>

            <RadioGroup v-model="currentSourceOption" class="mt-2">
              <RadioGroupLabel class="sr-only">{{
                $t("type")
              }}</RadioGroupLabel>
              <div class="space-y-4">
                <RadioGroupOption
                  v-for="[source, metadata] in optionsMetadataIter"
                  :key="source"
                  v-slot="{ checked }"
                  as="template"
                  :value="source"
                >
                  <div
                    :class="[
                      'relative block cursor-pointer bg-zinc-800 rounded-lg border border-zinc-900 px-2 py-2 shadow-sm focus:outline-none sm:flex sm:justify-between',
                    ]"
                  >
                    <span class="flex items-center gap-x-4">
                      <div>
                        <component
                          :is="metadata.icon"
                          class="size-12 bg-zinc-900 rounded-xl p-3 text-zinc-400"
                        />
                      </div>
                      <span class="flex flex-col text-sm">
                        <RadioGroupLabel
                          as="span"
                          class="font-semibold text-zinc-100"
                          >{{ metadata.title }}
                          <span class="ml-2 font-mono text-zinc-500 text-xs">{{
                            source
                          }}</span></RadioGroupLabel
                        >
                        <RadioGroupDescription
                          as="span"
                          class="text-zinc-400 text-xs"
                        >
                          {{ metadata.description }}
                        </RadioGroupDescription>
                        <NuxtLink
                          :href="metadata.docsLink"
                          :external="true"
                          target="_blank"
                          class="mt-2 block w-fit rounded-md bg-blue-600 px-2 py-1 text-center text-xs font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
                        >
                          <i18n-t
                            keypath="library.admin.sources.documentationLink"
                            tag="span"
                            scope="global"
                            class="inline-flex items-center gap-x-1"
                          >
                            <template #arrow>
                              <ArrowTopRightOnSquareIcon class="size-4" />
                            </template>
                          </i18n-t>
                        </NuxtLink>
                      </span>
                    </span>
                    <span
                      :class="[
                        checked ? 'ring-2 ring-blue-600' : '',
                        'pointer-events-none absolute -inset-px rounded-lg',
                      ]"
                      aria-hidden="true"
                    />
                  </div>
                </RadioGroupOption>
              </div>
            </RadioGroup>
          </div>

          <div class="h-[1px] w-full bg-zinc-700 rounded-full" />
          <component
            :is="optionUIs[currentSourceOption]"
            v-model="sourceConfig"
          />

          <input type="submit" class="hidden" />
        </form>

        <div v-if="modalError" class="mt-3 rounded-md bg-red-600/10 p-4">
          <div class="flex">
            <div class="flex-shrink-0">
              <XCircleIcon class="h-5 w-5 text-red-600" aria-hidden="true" />
            </div>
            <div class="ml-3">
              <h3 class="text-sm font-medium text-red-600">
                {{ modalError }}
              </h3>
            </div>
          </div>
        </div>
      </template>

      <template #buttons="{ close }">
        <LoadingButton
          :loading="modalLoading"
          :disabled="modalLoading"
          class="w-full sm:w-fit"
          @click="() => performActionSource_wrapper()"
        >
          {{ createMode ? $t("common.create") : $t("common.save") }}
        </LoadingButton>
        <button
          ref="cancelButtonRef"
          type="button"
          class="mt-3 inline-flex w-full justify-center rounded-md bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-800 hover:bg-zinc-900 sm:mt-0 sm:w-auto"
          @click="
            () => {
              editIndex = undefined;
              close();
            }
          "
        >
          {{ $t("cancel") }}
        </button>
      </template>
    </ModalTemplate>
  </div>
</template>

<script setup lang="ts">
/**
 * I did something a little cursed for this
 * To avoid making a separate modal for saving, we
 * instead set the index of the source we want to edit
 * and there's a bunch of checks everywhere to switch
 * between 'create' and 'edit'
 */

import {
  DropLogo,
  SourceOptionsFilesystem,
  SourceOptionsFlatFilesystem,
} from "#components";
import {
  DialogTitle,
  RadioGroup,
  RadioGroupDescription,
  RadioGroupLabel,
  RadioGroupOption,
} from "@headlessui/vue";
import {
  XCircleIcon,
  ArrowTopRightOnSquareIcon,
} from "@heroicons/vue/20/solid";
import { BackwardIcon } from "@heroicons/vue/24/outline";
import { FetchError } from "ofetch";
import type { Component } from "vue";
import type { LibraryBackend } from "~/prisma/client/enums";
import type { WorkingLibrarySource } from "~/server/api/v1/admin/library/sources/index.get";

definePageMeta({
  layout: "admin",
});

useHead({
  title: "Library Sources",
});

const { t } = useI18n();

// Optional token for setup wizard
const { token = undefined } = defineProps<{ token?: string }>();

const headers = token ? { Authorization: token } : undefined;

const sources = ref(
  await $dropFetch<WorkingLibrarySource[]>("/api/v1/admin/library/sources", {
    headers,
  }),
);

const editIndex = ref<undefined | number>(undefined);
const createMode = computed(() => editIndex.value === undefined);

const actionSourceOpen = ref(false);
const currentSourceOption = ref<LibraryBackend>("Filesystem");
const sourceName = ref("");
const sourceConfig = ref<object>({});

const modalError = ref<undefined | string>();
const modalLoading = ref(false);

const optionUIs: { [key in LibraryBackend]: Component } = {
  Filesystem: SourceOptionsFilesystem,
  FlatFilesystem: SourceOptionsFlatFilesystem,
};
const optionsMetadata: {
  [key in LibraryBackend]: {
    title: string;
    description: string;
    docsLink: string;
    icon: Component;
  };
} = {
  Filesystem: {
    title: t("library.admin.sources.fsTitle"),
    description: t("library.admin.sources.fsDesc"),
    docsLink: "https://droposs.org/docs/reference/library-sources#drop-style",
    icon: DropLogo,
  },
  FlatFilesystem: {
    title: t("library.admin.sources.fsFlatTitle"),
    description: t("library.admin.sources.fsFlatDesc"),
    docsLink:
      "https://droposs.org/docs/reference/library-sources#compatibility-flat-style",
    icon: BackwardIcon,
  },
};
const optionsMetadataIter = Object.entries(optionsMetadata);

async function performActionSource() {
  const createMode = editIndex.value === undefined;

  const source = await $dropFetch<WorkingLibrarySource>(
    "/api/v1/admin/library/sources",
    {
      body: {
        id: createMode ? undefined : sources.value[editIndex.value!].id,
        name: sourceName.value,
        backend: createMode ? currentSourceOption.value : undefined,
        options: sourceConfig.value,
      },
      method: createMode ? "POST" : "PATCH",
      headers,
    },
  );
  if (createMode) {
    sources.value.push(source);
  } else {
    sources.value[editIndex.value!] = source;
  }
}

function performActionSource_wrapper() {
  modalError.value = undefined;
  modalLoading.value = true;
  performActionSource()
    .then(() => {
      actionSourceOpen.value = false;
      sourceConfig.value = {};
      sourceName.value = "";
    })
    .catch((e) => {
      if (e instanceof FetchError) {
        modalError.value = e.statusMessage ?? e.message;
      } else {
        modalError.value = e as string;
      }
    })
    .finally(() => {
      modalLoading.value = false;
    });
}

function edit(index: number) {
  const source = sources.value[index];
  if (!source) return;

  sourceName.value = source.name;
  sourceConfig.value = source.options! as object;

  editIndex.value = index;
  actionSourceOpen.value = true;
}

function deleteSource(index: number) {
  const source = sources.value[index];
  if (!source) return;

  createModal(
    ModalType.Confirmation,
    {
      title: t("library.admin.sources.deleteTitle"),
      description: t("library.admin.sources.deleteDesc", [source.name]),
      buttonText: t("library.admin.sources.deleteButton"),
    },
    async (event, close) => {
      if (event !== "confirm") return close();

      try {
        await $dropFetch("/api/v1/admin/library/sources", {
          method: "DELETE",
          body: { id: source.id },
          headers,
        });
      } catch (e) {
        createModal(
          ModalType.Notification,
          {
            title: t("errors.library.source.delete.title"),
            description: t("errors.library.source.delete.desc", [
              // @ts-expect-error attempt to display statusMessage on error
              e?.statusMessage ?? t("errors.unknown"),
            ]),
          },
          (_, c) => c(),
        );
        return close();
      }

      const currentIndex = sources.value.findIndex((s) => s.id === source.id);
      if (currentIndex !== -1) sources.value.splice(currentIndex, 1);
      close();
    },
  );
}
</script>
