<script setup lang="ts">
import { useAuthSession } from '../../../composables/useAuthSession'

definePageMeta({ title: '资料' })

const { refresh: refreshAuthSession } = useAuthSession()
const { fetchData, getApiErrorMessage } = useClientApi()
const toast = useToast()

type ProfileGet = {
  profile: {
    nickname: string | null
    avatar: string | null
    avatarVisibility: string
    bioMarkdown: string | null
    bioVisibility: string
    socialLinks: { label: string; url: string; visibility: string; icon?: string }[]
    publicSlug: string | null
    discoverVisible: boolean
    discoverLocation: string | null
    discoverShowLocation: boolean
  }
}

type MeConfigGet = {
  config: Record<string, unknown>
}

const state = reactive({
  nickname: '',
  avatar: '',
  avatarVisibility: 'private',
  bioMarkdown: '',
  bioVisibility: 'private',
  publicSlug: '',
  discoverVisible: true,
  discoverLocation: '',
  discoverShowLocation: false,
  linksJson: '[]',
  publicHomeHeaderTitle: '',
  publicHomeHeaderIconUrl: '',
})

const loading = ref(true)
const saving = ref(false)
const uploadingAvatar = ref(false)
const uploadingHeaderIcon = ref(false)
const avatarFileInput = ref<HTMLInputElement | null>(null)
const headerIconFileInput = ref<HTMLInputElement | null>(null)

const imageCropOpen = ref(false)
const imageCropFile = ref<File | null>(null)
const imageCropVariant = ref<'avatar' | 'headerIcon'>('avatar')

watch(imageCropOpen, (isOpen) => {
  if (!isOpen) {
    imageCropFile.value = null
  }
})

const BIO_PREVIEW_MAX_CHARS = 200
const bioModalOpen = ref(false)
const bioDraft = ref('')

function truncateBioSource(md: string): string {
  const s = md.trim()
  if (!s) {
    return ''
  }
  if (s.length <= BIO_PREVIEW_MAX_CHARS) {
    return s
  }
  return `${s.slice(0, BIO_PREVIEW_MAX_CHARS)}…`
}

const bioPreviewTruncated = computed(() => truncateBioSource(state.bioMarkdown))

function openBioModal() {
  bioDraft.value = state.bioMarkdown
  bioModalOpen.value = true
}

function applyBioFromModal(close: () => void) {
  state.bioMarkdown = bioDraft.value
  close()
}

function openAvatarPicker() {
  avatarFileInput.value?.click()
}

function openHeaderIconPicker() {
  headerIconFileInput.value?.click()
}

function onAvatarFileChange(ev: Event) {
  const input = ev.target as HTMLInputElement
  const file = input.files?.[0]
  input.value = ''
  if (!file) {
    return
  }
  imageCropVariant.value = 'avatar'
  imageCropFile.value = file
  imageCropOpen.value = true
}

function onHeaderIconFileChange(ev: Event) {
  const input = ev.target as HTMLInputElement
  const file = input.files?.[0]
  input.value = ''
  if (!file) {
    return
  }
  imageCropVariant.value = 'headerIcon'
  imageCropFile.value = file
  imageCropOpen.value = true
}

async function uploadCroppedAvatar(file: File) {
  uploadingAvatar.value = true
  try {
    const form = new FormData()
    form.append('file', file)
    const { files } = await fetchData<{ files: { url: string }[] }>('/api/file/upload', {
      method: 'POST',
      body: form,
      notify: false,
    })
    const url = files[0]?.url
    if (!url) {
      toast.add({ title: '上传未返回地址', color: 'error' })
      return
    }
    state.avatar = url
    toast.add({ title: '头像已上传，请保存资料', color: 'success' })
  } catch (e: unknown) {
    toast.add({ title: getApiErrorMessage(e), color: 'error' })
  } finally {
    uploadingAvatar.value = false
  }
}

async function uploadCroppedHeaderIcon(file: File) {
  uploadingHeaderIcon.value = true
  try {
    const form = new FormData()
    form.append('file', file)
    const { files } = await fetchData<{ files: { url: string }[] }>('/api/file/upload', {
      method: 'POST',
      body: form,
      notify: false,
    })
    const url = files[0]?.url
    if (!url) {
      toast.add({ title: '上传未返回地址', color: 'error' })
      return
    }
    state.publicHomeHeaderIconUrl = url
    toast.add({ title: '顶栏图标已上传，请保存资料', color: 'success' })
  } catch (e: unknown) {
    toast.add({ title: getApiErrorMessage(e), color: 'error' })
  } finally {
    uploadingHeaderIcon.value = false
  }
}

async function onImageCropConfirm(file: File) {
  if (imageCropVariant.value === 'avatar') {
    await uploadCroppedAvatar(file)
  } else {
    await uploadCroppedHeaderIcon(file)
  }
}

async function load() {
  loading.value = true
  try {
    const [profilePayload, meCfgPayload] = await Promise.all([
      fetchData<ProfileGet>('/api/me/profile'),
      fetchData<MeConfigGet>('/api/config/me'),
    ])
    const p = profilePayload.profile
    const cfg = meCfgPayload.config
    state.nickname = p.nickname ?? ''
    state.avatar = p.avatar ?? ''
    state.avatarVisibility = p.avatarVisibility
    state.bioMarkdown = p.bioMarkdown ?? ''
    state.bioVisibility = p.bioVisibility
    state.publicSlug = p.publicSlug ?? ''
    state.discoverVisible = Boolean(p.discoverVisible)
    state.discoverLocation = p.discoverLocation ?? ''
    state.discoverShowLocation = Boolean(p.discoverShowLocation)
    state.linksJson = JSON.stringify(p.socialLinks ?? [], null, 2)
    state.publicHomeHeaderTitle = typeof cfg.publicHomeHeaderTitle === 'string' ? cfg.publicHomeHeaderTitle : ''
    state.publicHomeHeaderIconUrl =
      typeof cfg.publicHomeHeaderIconUrl === 'string' ? cfg.publicHomeHeaderIconUrl : ''
  } finally {
    loading.value = false
  }
}

onMounted(load)

async function save() {
  saving.value = true
  try {
    let links: { label: string; url: string; visibility: string; icon?: string }[] = []
    try {
      links = JSON.parse(state.linksJson) as { label: string; url: string; visibility: string; icon?: string }[]
    } catch {
      toast.add({ title: '社交链接 JSON 无效', color: 'error' })
      return
    }
    await fetchData('/api/me/profile', {
      method: 'PUT',
      notify: false,
      body: {
        nickname: state.nickname || null,
        avatar: state.avatar || null,
        avatarVisibility: state.avatarVisibility,
        bioMarkdown: state.bioMarkdown || null,
        bioVisibility: state.bioVisibility,
        publicSlug: state.publicSlug || null,
        discoverVisible: state.discoverVisible,
        discoverLocation: state.discoverLocation.trim() || null,
        discoverShowLocation: state.discoverShowLocation,
        socialLinks: links,
      },
    })
    await Promise.all([
      fetchData('/api/config/me', {
        method: 'PUT',
        notify: false,
        body: { key: 'publicHomeHeaderTitle', value: state.publicHomeHeaderTitle },
      }),
      fetchData('/api/config/me', {
        method: 'PUT',
        notify: false,
        body: { key: 'publicHomeHeaderIconUrl', value: state.publicHomeHeaderIconUrl },
      }),
    ])
    toast.add({ title: '资料已保存', color: 'success' })
    try {
      await refreshAuthSession(true)
    } catch {
      /* 顶栏会话与表单仍可能不一致，但不把已成功写入误判为保存失败 */
    }
    await load()
  } catch (e: unknown) {
    toast.add({ title: getApiErrorMessage(e), color: 'error' })
  } finally {
    saving.value = false
  }
}
</script>

<template>
  <UContainer class="py-8 max-w-2xl space-y-6">
    <h1 class="text-2xl font-semibold">
      个人资料
    </h1>
    <div class="relative min-h-[28rem]">
      <div
        v-show="loading"
        class="absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 rounded-lg bg-default/75 backdrop-blur-[2px]"
        aria-live="polite"
        aria-busy="true"
      >
        <UIcon name="i-lucide-loader-2" class="size-8 animate-spin text-primary" />
        <span class="text-sm text-muted">加载中…</span>
      </div>
      <UForm
        :state="state"
        class="space-y-4"
        :class="loading ? 'pointer-events-none select-none opacity-50' : ''"
        @submit.prevent="save"
      >
      <UFormField label="公开主页 slug（/@slug）" name="publicSlug">
        <UInput v-model="state.publicSlug" placeholder="例如 my-id" />
      </UFormField>
      <UAlert
        v-if="state.discoverVisible && !state.publicSlug.trim()"
        color="warning"
        variant="subtle"
        title="尚未设置公开主页 slug"
        description="保存「出现在发现页」后，仍需填写上方的 slug 才会真正出现在发现列表中。"
        class="rounded-lg"
      />
      <div class="space-y-4 rounded-lg border border-default bg-elevated/20 p-4">
        <h2 class="text-sm font-semibold text-highlighted">
          发现与展示
        </h2>
        <UCheckbox v-model="state.discoverVisible" label="出现在发现页" />
        <UFormField
          label="地址或地区（展示文案）"
          name="discoverLocation"
          description="将显示在你的发现卡片上（若下方选项开启）。无需门牌号等精确信息。"
        >
          <UInput v-model="state.discoverLocation" maxlength="200" placeholder="例如：上海" />
        </UFormField>
        <UCheckbox
          v-model="state.discoverShowLocation"
          :disabled="!state.discoverVisible"
          label="在发现卡片上显示上述地址"
        />
      </div>
      <UFormField label="昵称" name="nickname">
        <UInput v-model="state.nickname" />
      </UFormField>
      <UFormField
        label="公开主页顶栏名称"
        name="publicHomeHeaderTitle"
        description="在 /@你的 slug 页面左上角显示。留空则使用全站站点名称。"
      >
        <UInput v-model="state.publicHomeHeaderTitle" maxlength="64" placeholder="可选，默认与站点名称相同" />
      </UFormField>
      <UFormField
        label="公开主页顶栏图标"
        name="publicHomeHeaderIconUrl"
        description="留空则显示默认图标（与站点顶栏一致）。支持站内路径或 http(s) 链接。"
      >
        <div class="flex flex-col gap-3 sm:flex-row sm:items-start">
          <span class="flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-primary/10 text-primary ring-1 ring-default">
            <img
              v-if="state.publicHomeHeaderIconUrl"
              :src="state.publicHomeHeaderIconUrl"
              alt=""
              class="h-full w-full object-cover"
            >
            <UIcon v-else name="i-lucide-orbit" class="size-5" />
          </span>
          <div class="min-w-0 flex-1 space-y-2">
            <div class="flex flex-wrap items-center gap-2">
              <input
                ref="headerIconFileInput"
                type="file"
                class="sr-only"
                accept="image/png,image/jpeg,image/jpg,image/webp"
                @change="onHeaderIconFileChange"
              >
              <UButton type="button" :loading="uploadingHeaderIcon" @click="openHeaderIconPicker">
                上传图片
              </UButton>
              <span class="text-xs text-muted">PNG / JPG / WebP，单张最大 5MB</span>
            </div>
            <UInput v-model="state.publicHomeHeaderIconUrl" placeholder="或直接填写图片 URL" />
          </div>
        </div>
      </UFormField>
      <UFormField label="头像" name="avatar">
        <div class="flex flex-col gap-3 sm:flex-row sm:items-start">
          <UAvatar :src="state.avatar || undefined" size="xl" class="ring-1 ring-default shrink-0" />
          <div class="min-w-0 flex-1 space-y-2">
            <div class="flex flex-wrap items-center gap-2">
              <input
                ref="avatarFileInput"
                type="file"
                class="sr-only"
                accept="image/png,image/jpeg,image/jpg,image/webp"
                @change="onAvatarFileChange"
              >
              <UButton type="button" :loading="uploadingAvatar" @click="openAvatarPicker">
                上传图片
              </UButton>
              <span class="text-xs text-muted">PNG / JPG / WebP，单张最大 5MB</span>
            </div>
            <UInput v-model="state.avatar" placeholder="或直接填写图片 URL" />
          </div>
        </div>
      </UFormField>
      <UFormField label="头像可见性" name="avatarVisibility">
        <USelect
          v-model="state.avatarVisibility"
          :items="[
            { label: '私密', value: 'private' },
            { label: '公开', value: 'public' },
            { label: '仅链接', value: 'unlisted' },
          ]"
        />
      </UFormField>
      <UFormField
        label="生平 / 简介（Markdown）"
        name="bioMarkdown"
        description="在弹窗中使用 Markdown 编辑器修改；下方仅展示原文前若干字的截断预览。"
      >
        <div class="space-y-2 rounded-lg border border-default bg-elevated/30 p-3">
          <p
            class="max-h-32 overflow-y-auto whitespace-pre-wrap break-words font-mono text-sm text-muted"
          >
            {{ bioPreviewTruncated || '（未填写）' }}
          </p>
          <UButton type="button" variant="outline" size="sm" icon="i-lucide-square-pen" @click="openBioModal">
            在编辑器中打开
          </UButton>
        </div>
      </UFormField>
      <UFormField label="简介可见性" name="bioVisibility">
        <USelect
          v-model="state.bioVisibility"
          :items="[
            { label: '私密', value: 'private' },
            { label: '公开', value: 'public' },
            { label: '仅链接', value: 'unlisted' },
          ]"
        />
      </UFormField>
      <UFormField
        label="社交链接（JSON 数组）"
        name="linksJson"
        description="每条可含可选 icon，须为 Nuxt Icon 全名（小写），如 i-lucide-heart、i-simple-icons-github；省略则按 URL 自动匹配图标。"
      >
        <UTextarea v-model="state.linksJson" :rows="8" class="w-full font-mono text-sm" />
      </UFormField>
      <UButton type="submit" :loading="saving" :disabled="loading">
        保存
      </UButton>
    </UForm>
    </div>

    <ImageCropModal
      v-model:open="imageCropOpen"
      :file="imageCropFile"
      :variant="imageCropVariant"
      @confirm="onImageCropConfirm"
    />

    <UModal
      v-model:open="bioModalOpen"
      title="编辑生平 / 简介"
      description="支持 Markdown；图片可粘贴或上传至站内。"
      class="w-[calc(100vw-2rem)] sm:max-w-4xl"
    >
      <template #body>
        <PostBodyMarkdownEditor v-model="bioDraft" />
      </template>
      <template #footer="{ close }">
        <div class="flex justify-end gap-2">
          <UButton type="button" variant="outline" color="neutral" @click="close">
            取消
          </UButton>
          <UButton type="button" @click="applyBioFromModal(close)">
            完成
          </UButton>
        </div>
      </template>
    </UModal>
  </UContainer>
</template>
