<script setup lang="ts">
import type { FormError } from '#ui/types';
import CodeSegment from '~/components/api/CodeSegment.vue';
import { apis } from '~/config/public-apis';

interface Props {
  initialSelected: string;
}
const props = defineProps<Props>();

const isOpen = ref(false);
const selectedApi = ref(apis[0]);

const payload: Ref<Record<string, any>> = ref({});

const host = window.location.protocol + '//' + window.location.host;

function onOpen() {
  isOpen.value = true;
  selectedApi.value = apis.find(api => api.name === props.initialSelected)!;
}

function apiChange() {
  payload.value = {};
}

function isEmpty(key: string, obj: Record<string, any>): boolean {
  if (!obj.hasOwnProperty(key)) {
    return true;
  }
  let value = obj[key];
  if (typeof value === 'string') {
    value = value.trim();
  }
  return value === undefined || value === null || value === '';
}

function validate(state: Record<string, any>): FormError[] {
  const errors: FormError[] = [];

  selectedApi.value.params.forEach(param => {
    if (param.required && isEmpty(param.name, state)) {
      errors.push({ path: param.name, message: param.name + '不能为空' });
    }
    if (!isEmpty(param.name, state) && param.type === 'Int') {
      if (Number.isNaN(parseInt(state[param.name]))) {
        errors.push({ path: param.name, message: param.name + '格式不正确' });
      } else if (parseInt(state[param.name]) < 0) {
        errors.push({ path: param.name, message: param.name + '不得小于0' });
      }
    }
  });

  return errors;
}

const resp = ref<Record<string, any> | null | string>(null);
const hasResponse = computed(() => {
  return typeof resp.value === 'string' || (typeof resp.value === 'object' && resp.value);
});
const btnLoading = ref(false);
function submit() {
  const params = toRaw(payload.value);

  let url = selectedApi.value.url;
  if (selectedApi.value.method === 'GET') {
    url += '?' + new URLSearchParams(params).toString();
  }

  btnLoading.value = true;
  resp.value = null;
  fetch(url, {
    method: selectedApi.value.method,
  })
    .then(resp => {
      if (resp.headers.get('content-type') === 'application/json') {
        return resp.json();
      } else {
        return resp.text();
      }
    })
    .then(data => {
      resp.value = data;
    })
    .finally(() => {
      btnLoading.value = false;
    });
}
</script>

<template>
  <div>
    <UTooltip text="在线调试" :popper="{ placement: 'top' }">
      <UButton color="blue" variant="ghost" square @click="onOpen" icon="i-lucide:bug-play"></UButton>
    </UTooltip>

    <USlideover v-model="isOpen" :ui="{ width: 'max-w-[800px]' }">
      <UCard
        class="flex flex-col flex-1"
        :ui="{
          body: { base: 'overflow-y-scroll h-[calc(100vh-72px)]' },
          ring: '',
          divide: 'divide-y divide-gray-100 dark:divide-gray-800',
        }"
      >
        <template #header>
          <h1 class="font-semibold text-2xl">在线接口调试工具</h1>
        </template>

        <div class="space-y-5">
          <div class="flex items-center gap-3">
            <span>选择接口: </span>
            <USelectMenu
              class="flex-1"
              v-model="selectedApi"
              :options="apis"
              option-attribute="name"
              @change="apiChange"
            >
              <template #label>
                <span
                  :class="[
                    selectedApi.method === 'GET' ? 'bg-green-400' : 'bg-fuchsia-400',
                    'inline-block size-2 flex-shrink-0 rounded-full',
                  ]"
                  aria-hidden="true"
                />
                <span class="font-medium">{{ selectedApi.name }}</span>
              </template>
              <template #option="{ option: api }">
                <div>
                  <p class="font-medium">
                    <span
                      :class="[
                        api.method === 'GET' ? 'bg-green-400' : 'bg-fuchsia-400',
                        'inline-block size-2 mr-2 flex-shrink-0 rounded-full',
                      ]"
                      aria-hidden="true"
                    />
                    <span>{{ api.name }}</span>
                  </p>
                  <p class="text-gray-400 font-mono">{{ api.description }}</p>
                </div>
              </template>
            </USelectMenu>
          </div>

          <div class="space-y-5">
            <div>
              <p class="font-semibold mb-2">请求URL:</p>
              <p class="font-mono border p-2 rounded-md">
                <span class="text-gray-400">{{ host }}</span>
                <span class="font-semibold">{{ selectedApi.url }}</span>
              </p>
            </div>
            <div>
              <p class="font-semibold mb-2">请求方式:</p>
              <p class="font-mono border p-2 rounded-md">{{ selectedApi.method }}</p>
            </div>
            <div>
              <p class="font-semibold mb-2">参数:</p>
              <UForm :state="payload" :validate="validate" @submit="submit" class="space-y-3">
                <UFormGroup
                  v-for="p in selectedApi.params"
                  :key="p.name"
                  :label="p.name"
                  :name="p.name"
                  :required="p.required"
                >
                  <UInput
                    v-model="payload[p.name]"
                    :type="p.type === 'Int' ? 'number' : 'text'"
                    :placeholder="p.label + (p.remark ? '，' + p.remark : '')"
                  />
                </UFormGroup>
                <UButton type="submit" color="black" class="px-5" :loading="btnLoading">提交</UButton>
              </UForm>
            </div>
          </div>
          <div v-if="hasResponse">
            <h3 class="font-bold text-2xl">响应</h3>
            <CodeSegment :code="resp!" :lang="typeof resp === 'object' ? 'json' : 'xml'" />
          </div>
        </div>
      </UCard>
    </USlideover>
  </div>
</template>
