<!-- eslint-disable vue/multi-word-component-names -->
<template>
  <div class="card">
    <header class="card-header is-unselectable" style="cursor: pointer">
      <p class="card-header-title" @click="toggle">{{ header }}</p>

      <p v-if="refreshable && is_open" class="card-header-icon px-0">
        <BulmaButton class="is-small is-primary" @click="load">
          <FontAwesomeIcon
            :icon="['fas', 'arrows-rotate']"
            :spin="state === 'loading'"
          />
        </BulmaButton>
      </p>

      <button class="card-header-icon" @click="toggle">
        <span class="icon">
          <FontAwesomeIcon
            :icon="['fas', is_open ? 'angle-down' : 'angle-right']"
          />
        </span>
      </button>
    </header>

    <slot v-if="state === 'loading'" name="loading">
      <div class="card-content">
        <progress class="progress is-primary" />
      </div>
    </slot>

    <slot v-else-if="state === 'err'" name="error">
      <div class="card-content has-text-danger has-text-centered">
        <span class="icon is-large">
          <FontAwesomeIcon :icon="['fas', 'ban']" size="3x" />
        </span>
      </div>
    </slot>

    <slot v-else-if="state === 'ok'" name="default" />
  </div>
</template>

<script setup lang="ts">
import { computed, ref } from "vue";

import BulmaButton from "./Button.vue";

const props = withDefaults(
  defineProps<{
    header: string;
    opening?: () => Promise<void>;
    refreshable?: boolean;
  }>(),
  { opening: async () => {}, refreshable: false },
);

const state = ref<"closed" | "loading" | "ok" | "err">("closed");
const is_open = computed(() => state.value !== "closed");

async function toggle(): Promise<void> {
  switch (state.value) {
    case "closed":
      // start opening when closed
      await load();
      break;

    case "loading":
      // don't toggle when loading
      break;

    default:
      state.value = "closed";
      break;
  }
}

async function load(): Promise<void> {
  state.value = "loading";

  try {
    await props.opening();
    state.value = "ok";
  } catch {
    state.value = "err";
  }
}
</script>

<style scoped>
div.card:not(:last-child) {
  margin-bottom: 1.5rem;
}
</style>
