<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-8 flow-root">
      <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
        <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
          <table class="min-w-full divide-y divide-zinc-700">
            <thead>
              <tr>
                <th
                  scope="col"
                  class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-zinc-100 sm:pl-3"
                >
                  {{ $t("common.name") }}
                </th>
                <th
                  scope="col"
                  class="px-3 py-3.5 text-left text-sm font-semibold text-zinc-100"
                >
                  {{ $t("type") }}
                </th>
                <th
                  scope="col"
                  class="px-3 py-3.5 text-left text-sm font-semibold text-zinc-100"
                >
                  {{ $t("library.admin.sources.working") }}
                </th>
                <th
                  scope="col"
                  class="px-3 py-3.5 text-left text-sm font-semibold text-zinc-100"
                >
                  {{ $t("options") }}
                </th>
                <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-3">
                  <span class="sr-only">{{ $t("common.edit") }}</span>
                </th>
              </tr>
            </thead>
            <tbody>
              <tr
                v-for="(source, sourceIdx) in sources"
                :key="source.id"
                class="even:bg-zinc-800"
              >
                <td
                  class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-zinc-100 sm:pl-3"
                >
                  {{ source.name }}
                </td>
                <td
                  class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400 inline-flex gap-x-1 items-center"
                >
                  <component
                    :is="optionsMetadata[source.backend].icon"
                    class="size-5 text-zinc-400"
                  />
                  {{ optionsMetadata[source.backend].title }}
                </td>
                <td class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400">
                  <CheckIcon
                    v-if="source.working"
                    class="size-5 text-green-500"
                  />
                  <XMarkIcon v-else class="size-5 text-red-500" />
                </td>
                <td class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400">
                  {{ source.options }}
                </td>
                <td
                  class="relative whitespace-nowrap py-4 pl-3 pr-3 text-right text-sm font-medium space-x-2"
                >
                  <button
                    class="text-blue-500 hover:text-blue-400"
                    @click="() => edit(sourceIdx)"
                  >
                    {{ $t("common.edit") }}
                    <span class="sr-only">
                      {{ $t("chars.srComma", [source.name]) }}
                    </span>
                  </button>

                  <button
                    class="text-red-500 hover:text-red-400"
                    @click="() => deleteSource(sourceIdx)"
                  >
                    {{ $t("delete") }}
                    <span class="sr-only">
                      {{ $t("chars.srComma", [source.name]) }}
                    </span>
                  </button>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
    </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, CheckIcon, XMarkIcon } 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://docs.droposs.org/docs/library#drop-style",
    icon: DropLogo,
  },
  FlatFilesystem: {
    title: t("library.admin.sources.fsFlatTitle"),
    description: t("library.admin.sources.fsFlatDesc"),
    docsLink: "https://docs.droposs.org/docs/library#flat-style-or-compat",
    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 = "";
      },
      (e) => {
        if (e instanceof FetchError) {
                  console.log(e.data.message);
          modalError.value = 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;
}

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

  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 message on error
          e?.message ?? t("errors.unknown"),
        ]),
      },
      (_, c) => c(),
    );
  }

  sources.value.splice(index, 1);
}
</script>
