<!-- 用户账户页，负责资料维护、密码修改、兑换码和积分记录展示。 -->
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, reactive, ref } from "vue";
import type { FormInstance, FormRules } from "element-plus";
import { Camera, Key, Lock, Pencil, Wallet } from "@/icons";
import AccountSectionState from "@/components/account/AccountSectionState.vue";
import PointRecordList from "@/components/account/PointRecordList.vue";
import type { PointRecord, PointRecordPage } from "@/components/account/types";
import ConsumptionChart from "@/components/ConsumptionChart.vue";
import http, { apiError } from "@/api/http";
import { useAuthStore } from "@/stores/auth";
import { useToastStore } from "@/stores/toast";
import { formatDate } from "@/utils/datetime";

/** AccountData 描述用户中心依赖的账户资料、积分额度和头像限制。 */
interface AccountData {
  uid?: string;
  username?: string;
  account?: string;
  avatar_url?: string;
  point_balance?: string | number;
  today_consumption?: string | number;
  daily_limit?: string | number;
  avatar_max_size_bytes?: number;
}

/** UsageItem 描述一天的积分消耗汇总。 */
interface UsageItem {
  day: string;
  consumption: string | number;
}

const auth = useAuthStore();
const toast = useToastStore();
const initializing = ref(true);
const accountLoading = ref(false);
const usageLoading = ref(false);
const recordsLoading = ref(false);
const dialogRecordsLoading = ref(false);
const accountReady = ref(false);
const usageReady = ref(false);
const recordsReady = ref(false);
const accountError = ref("");
const usageError = ref("");
const recordsError = ref("");
const dialogRecordsError = ref("");
const avatarUploading = ref(false);
const profileSaving = ref(false);
const passwordSaving = ref(false);
const editingUsername = ref(false);
const usernameError = ref("");
const avatarDialogVisible = ref(false);
const passwordDialogVisible = ref(false);
const recordsDialogVisible = ref(false);
const avatarInput = ref<HTMLInputElement>();
const passwordFormRef = ref<FormInstance>();
const pendingAvatarFile = ref<File>();
const avatarPreviewUrl = ref("");
const redeeming = ref(false);
const code = ref("");
const usage = ref<UsageItem[]>([]);
const recentRecords = ref<PointRecord[]>([]);
const dialogRecords = ref<PointRecord[]>([]);
const recordsTotal = ref(0);
const recordsPage = ref(1);
const pageSize = 10;

const profileForm = reactive({ username: "" });
const passwordForm = reactive({ originalPassword: "", newPassword: "", confirmPassword: "" });
const passwordRules: FormRules = {
  originalPassword: [{ required: true, message: "请输入原密码", trigger: "blur" }],
  newPassword: [
    { required: true, message: "请输入新密码", trigger: "blur" },
    { min: 8, max: 128, message: "密码长度必须为 8～128 位", trigger: "blur" },
  ],
  confirmPassword: [
    { required: true, message: "请再次输入新密码", trigger: "blur" },
    {
      validator: (_rule, value, callback) =>
        value === passwordForm.newPassword ? callback() : callback(new Error("两次输入的新密码不一致")),
      trigger: "blur",
    },
  ],
};

const account = computed(() => auth.account as AccountData | null);
const labels = computed(() => usage.value.map((item) => formatDate(item.day)));
const values = computed(() => usage.value.map((item) => Number(item.consumption)));
const totalConsumption = computed(() => values.value.reduce((sum, value) => sum + value, 0).toFixed(2));
const hasConsumption = computed(() => values.value.some((value) => value > 0));
const balance = computed(() => Number(account.value?.point_balance || 0).toFixed(2));
const today = computed(() => Number(account.value?.today_consumption || 0).toFixed(2));
const unlimited = computed(() => Number(account.value?.daily_limit) === 9999);
const limit = computed(() => (unlimited.value ? "不限制" : Number(account.value?.daily_limit || 0).toFixed(2)));
const remaining = computed(() =>
  unlimited.value
    ? "不限制"
    : Math.max(Number(account.value?.daily_limit || 0) - Number(account.value?.today_consumption || 0), 0).toFixed(2),
);
const avatarMaxSizeBytes = computed(() => Number(account.value?.avatar_max_size_bytes || 0));
const avatarLimitText = computed(() =>
  avatarMaxSizeBytes.value > 0
    ? `支持 JPEG、PNG、WebP，文件不超过 ${(avatarMaxSizeBytes.value / 1024 / 1024).toFixed(0)} MB`
    : "头像上传暂不可用",
);

/** loadAccountData 加载账户资料和额度，失败时保留明确错误状态而不是显示默认零值。 */
async function loadAccountData() {
  accountLoading.value = true;
  accountError.value = "";
  try {
    const { data } = await http.get("/account");
    auth.account = data.data;
    profileForm.username = String(account.value?.username || account.value?.account || "");
    accountReady.value = true;
  } catch (error) {
    accountReady.value = false;
    accountError.value = apiError(error).message || "账户信息加载失败";
  } finally {
    accountLoading.value = false;
  }
}

/** loadUsageData 加载近三十日消耗，失败时仅影响趋势分区。 */
async function loadUsageData() {
  usageLoading.value = true;
  usageError.value = "";
  try {
    const { data } = await http.get("/usage/30-days");
    usage.value = Array.isArray(data.data) ? data.data : [];
    usageReady.value = true;
  } catch (error) {
    usageReady.value = false;
    usageError.value = apiError(error).message || "消耗数据加载失败";
  } finally {
    usageLoading.value = false;
  }
}

/** requestRecords 按页读取积分流水并校验分页响应结构。 */
async function requestRecords(page: number) {
  const { data } = await http.get("/usage/consumption-records", { params: { page, page_size: pageSize } });
  return data.data as PointRecordPage;
}

/** loadRecentRecords 加载用户中心摘要使用的最近十条积分流水。 */
async function loadRecentRecords() {
  recordsLoading.value = true;
  recordsError.value = "";
  try {
    const result = await requestRecords(1);
    recentRecords.value = Array.isArray(result.items) ? result.items : [];
    recordsTotal.value = Number(result.total || 0);
    recordsReady.value = true;
  } catch (error) {
    recordsReady.value = false;
    recordsError.value = apiError(error).message || "积分记录加载失败";
  } finally {
    recordsLoading.value = false;
  }
}

/** loadDialogRecords 加载完整流水弹窗当前页，并提供弹窗内重试状态。 */
async function loadDialogRecords(page = recordsPage.value) {
  dialogRecordsLoading.value = true;
  dialogRecordsError.value = "";
  try {
    const result = await requestRecords(page);
    dialogRecords.value = Array.isArray(result.items) ? result.items : [];
    recordsTotal.value = Number(result.total || 0);
    recordsPage.value = Number(result.page || page);
  } catch (error) {
    dialogRecordsError.value = apiError(error).message || "积分记录加载失败";
  } finally {
    dialogRecordsLoading.value = false;
  }
}

/** load 初始化用户中心各分区，允许单个接口失败而不拖垮其他内容。 */
async function load() {
  initializing.value = true;
  await Promise.allSettled([loadAccountData(), loadUsageData(), loadRecentRecords()]);
  initializing.value = false;
}

/** profileInitials 生成无头像时展示的用户名首字母。 */
function profileInitials() {
  return String(account.value?.username || account.value?.account || "JH")
    .slice(0, 2)
    .toUpperCase();
}

/** startUsernameEdit 进入用户名编辑并恢复当前已保存内容。 */
function startUsernameEdit() {
  profileForm.username = String(account.value?.username || account.value?.account || "");
  usernameError.value = "";
  editingUsername.value = true;
}

/** cancelUsernameEdit 退出用户名编辑并清除未提交错误。 */
function cancelUsernameEdit() {
  usernameError.value = "";
  editingUsername.value = false;
}

/** saveUsername 校验并保存用户名，将错误直接反馈在输入字段附近。 */
async function saveUsername() {
  const username = profileForm.username.trim();
  if ([...username].length < 5 || [...username].length > 12) {
    usernameError.value = "用户名长度必须为 5～12 个字符";
    return;
  }
  usernameError.value = "";
  profileSaving.value = true;
  try {
    const { data } = await http.patch("/account/profile", { username });
    auth.account = { ...account.value, ...data.data };
    auth.updateCachedProfile({ username: data.data.username });
    editingUsername.value = false;
    toast.success("用户名已更新");
  } catch (error) {
    const detail = apiError(error);
    usernameError.value = detail.message;
    toast.error(detail.message, detail.copyText, "用户名修改失败");
  } finally {
    profileSaving.value = false;
  }
}

/** chooseAvatar 打开头像文件选择器；存储不可用时保持控件禁用。 */
function chooseAvatar() {
  if (avatarMaxSizeBytes.value > 0) avatarInput.value?.click();
}

/** selectAvatar 校验头像格式和体积，并在上传前生成本地预览。 */
function selectAvatar(event: Event) {
  const input = event.target as HTMLInputElement;
  const file = input.files?.[0];
  input.value = "";
  if (!file) return;
  if (!["image/jpeg", "image/png", "image/webp"].includes(file.type)) {
    toast.warning("头像仅支持 JPEG、PNG 或 WebP 图片");
    return;
  }
  if (avatarMaxSizeBytes.value <= 0 || file.size > avatarMaxSizeBytes.value) {
    toast.warning(avatarLimitText.value);
    return;
  }
  resetAvatarPreview();
  pendingAvatarFile.value = file;
  avatarPreviewUrl.value = URL.createObjectURL(file);
  avatarDialogVisible.value = true;
}

/** uploadAvatar 上传预览确认后的头像并同步账户缓存。 */
async function uploadAvatar() {
  if (!pendingAvatarFile.value) return;
  avatarUploading.value = true;
  try {
    const form = new FormData();
    form.append("file", pendingAvatarFile.value);
    const { data } = await http.post("/account/avatar", form, {
      headers: { "Content-Type": "multipart/form-data" },
    });
    if (auth.account) auth.account = { ...auth.account, avatar_url: data.data.avatar_url };
    auth.updateCachedProfile({ avatar_url: data.data.avatar_url });
    avatarDialogVisible.value = false;
    toast.success("头像已更新");
  } catch (error) {
    showError(error, "头像修改失败");
  } finally {
    avatarUploading.value = false;
  }
}

/** resetAvatarPreview 释放本地预览地址并清除待上传文件。 */
function resetAvatarPreview() {
  if (avatarPreviewUrl.value) URL.revokeObjectURL(avatarPreviewUrl.value);
  avatarPreviewUrl.value = "";
  pendingAvatarFile.value = undefined;
}

/** openPasswordDialog 清空历史输入并打开密码修改弹窗。 */
function openPasswordDialog() {
  Object.assign(passwordForm, { originalPassword: "", newPassword: "", confirmPassword: "" });
  passwordDialogVisible.value = true;
}

/** changePassword 校验密码表单并提交新密码。 */
async function changePassword() {
  if (!(await passwordFormRef.value?.validate().catch(() => false))) return;
  passwordSaving.value = true;
  try {
    await http.put("/account/password", {
      original_password: passwordForm.originalPassword,
      new_password: passwordForm.newPassword,
    });
    passwordDialogVisible.value = false;
    toast.success("密码已修改");
  } catch (error) {
    showError(error, "密码修改失败");
  } finally {
    passwordSaving.value = false;
  }
}

/** redeem 提交兑换码并独立刷新账户、趋势和流水分区。 */
async function redeem() {
  if (!code.value.trim()) return;
  redeeming.value = true;
  try {
    const { data } = await http.post("/redemption/redeem", { code: code.value });
    toast.success(`兑换成功，已到账 ${Number(data.data.points).toFixed(2)} 积分`);
    code.value = "";
    await Promise.allSettled([loadAccountData(), loadUsageData(), loadRecentRecords()]);
    if (recordsDialogVisible.value) await loadDialogRecords(recordsPage.value);
  } catch (error) {
    showError(error, "兑换失败");
  } finally {
    redeeming.value = false;
  }
}

/** openRecordsDialog 打开流水弹窗并复用已加载的第一页数据。 */
function openRecordsDialog() {
  recordsPage.value = 1;
  dialogRecords.value = [...recentRecords.value];
  dialogRecordsError.value = "";
  recordsDialogVisible.value = true;
}

/** showError 通过统一消息组件展示操作失败详情。 */
function showError(error: unknown, title: string) {
  const detail = apiError(error);
  toast.error(detail.message, detail.copyText, title);
}

onMounted(load);
onBeforeUnmount(resetAvatarPreview);
</script>

<template>
  <div
    v-loading="initializing"
    class="web-page account-view"
  >
    <div class="account-dashboard">
      <section class="summary-grid">
        <article class="account-card balance-card">
          <header>
            <h2>创作额度</h2>
            <span v-if="accountReady">每日限额 {{ limit }}</span>
          </header>
          <AccountSectionState
            v-if="accountError"
            class="card-state"
            :message="accountError"
            @retry="loadAccountData"
          />
          <div
            v-else-if="accountReady"
            v-loading="accountLoading"
            class="metric-grid"
          >
            <div>
              <span>可用积分</span><strong>{{ balance }}</strong>
            </div>
            <div>
              <span>今日消耗</span><strong>{{ today }}</strong>
            </div>
            <div>
              <span>今日剩余可用</span><strong>{{ remaining }}</strong>
            </div>
          </div>
        </article>

        <article class="account-card profile-card">
          <header><h2>账户资料</h2></header>
          <AccountSectionState
            v-if="accountError"
            class="card-state"
            :message="accountError"
            @retry="loadAccountData"
          />
          <div
            v-else-if="accountReady"
            v-loading="accountLoading"
            class="profile-main"
          >
            <div class="avatar-control">
              <button
                v-loading="avatarUploading"
                class="avatar-button"
                type="button"
                :title="avatarLimitText"
                :disabled="avatarUploading || avatarMaxSizeBytes <= 0"
                @click="chooseAvatar"
              >
                <img
                  v-if="account?.avatar_url"
                  :src="account.avatar_url"
                  alt=""
                />
                <span v-else>{{ profileInitials() }}</span>
                <i><Camera :size="14" /></i>
              </button>
            </div>
            <input
              ref="avatarInput"
              class="avatar-input"
              type="file"
              accept="image/jpeg,image/png,image/webp"
              @change="selectAvatar"
            />
            <div class="profile-copy">
              <form
                v-if="editingUsername"
                class="username-edit"
                @submit.prevent="saveUsername"
              >
                <div class="username-field">
                  <el-input
                    v-model.trim="profileForm.username"
                    maxlength="12"
                    show-word-limit
                    @input="usernameError = ''"
                  />
                  <small :class="{ error: usernameError }">{{ usernameError || "用户名长度为 5～12 个字符" }}</small>
                </div>
                <el-button
                  native-type="submit"
                  :loading="profileSaving"
                  >保存</el-button
                >
                <el-button
                  native-type="button"
                  :disabled="profileSaving"
                  @click="cancelUsernameEdit"
                  >取消</el-button
                >
              </form>
              <div
                v-else
                class="username-display"
              >
                <strong>{{ account?.username || account?.account }}</strong>
                <button
                  type="button"
                  title="修改用户名"
                  @click="startUsernameEdit"
                >
                  <Pencil :size="16" />
                </button>
              </div>
              <p>UID {{ account?.uid || "--" }} · 账号 {{ account?.account || "--" }}</p>
            </div>
            <el-button
              class="password-button"
              @click="openPasswordDialog"
              ><Lock :size="16" />修改密码</el-button
            >
          </div>
        </article>

        <article class="account-card redeem-card">
          <header><h2>兑换码</h2></header>
          <form
            class="redeem-form"
            @submit.prevent="redeem"
          >
            <div class="redeem-field">
              <el-input
                v-model.trim="code"
                maxlength="64"
                placeholder="请输入兑换码"
              >
                <template #prefix><Key :size="16" /></template>
              </el-input>
            </div>
            <el-button
              native-type="submit"
              type="primary"
              :disabled="!code.trim() || redeeming"
              :loading="redeeming"
              ><Wallet :size="16" />立即兑换</el-button
            >
          </form>
        </article>
      </section>

      <section
        v-loading="usageLoading && !initializing"
        class="account-card chart-card"
      >
        <header>
          <div>
            <h2>近 30 日消耗</h2>
            <p v-if="usageReady">合计 {{ totalConsumption }} 积分</p>
          </div>
        </header>
        <AccountSectionState
          v-if="usageError"
          class="section-state"
          :message="usageError"
          @retry="loadUsageData"
        />
        <ConsumptionChart
          v-else-if="usageReady && hasConsumption"
          :labels="labels"
          :values="values"
        />
        <div
          v-else-if="usageReady"
          class="empty-state"
        >
          近 30 日暂无积分消耗
        </div>
      </section>

      <section
        v-loading="recordsLoading && !initializing"
        class="account-card records-card"
      >
        <header>
          <div>
            <h2>积分变动记录</h2>
            <p v-if="recordsReady">共 {{ recordsTotal }} 条记录</p>
          </div>
          <el-button
            v-if="recordsReady && recordsTotal > 0"
            text
            @click="openRecordsDialog"
            >查看全部</el-button
          >
        </header>
        <AccountSectionState
          v-if="recordsError"
          class="section-state"
          :message="recordsError"
          @retry="loadRecentRecords"
        />
        <PointRecordList
          v-else-if="recordsReady"
          class="records-summary"
          :records="recentRecords"
          empty-text="暂无积分变动记录，兑换或使用创作能力后会在这里显示"
        />
      </section>
    </div>

    <el-dialog
      v-model="recordsDialogVisible"
      title="积分变动记录"
      width="min(820px, calc(100vw - 32px))"
      align-center
      class="web-dialog records-dialog"
    >
      <div
        v-loading="dialogRecordsLoading"
        class="dialog-record-list"
      >
        <AccountSectionState
          v-if="dialogRecordsError"
          :message="dialogRecordsError"
          @retry="loadDialogRecords()"
        />
        <PointRecordList
          v-else
          :records="dialogRecords"
          empty-text="暂无积分变动记录"
        />
      </div>
      <template #footer>
        <el-pagination
          v-if="recordsTotal > pageSize"
          v-model:current-page="recordsPage"
          :page-size="pageSize"
          :pager-count="5"
          :total="recordsTotal"
          layout="prev, pager, next"
          @current-change="loadDialogRecords"
        />
      </template>
    </el-dialog>

    <el-dialog
      v-model="avatarDialogVisible"
      title="确认头像"
      width="min(420px, calc(100vw - 32px))"
      align-center
      class="web-dialog"
      @closed="resetAvatarPreview"
    >
      <div class="avatar-preview">
        <img
          v-if="avatarPreviewUrl"
          :src="avatarPreviewUrl"
          alt=""
        />
        <p>{{ avatarLimitText }}</p>
      </div>
      <template #footer>
        <el-button
          :disabled="avatarUploading"
          @click="avatarDialogVisible = false"
          >取消</el-button
        >
        <el-button
          type="primary"
          :loading="avatarUploading"
          @click="uploadAvatar"
          >确认上传</el-button
        >
      </template>
    </el-dialog>

    <el-dialog
      v-model="passwordDialogVisible"
      title="修改密码"
      width="min(460px, calc(100vw - 32px))"
      align-center
      class="web-dialog"
      @closed="passwordFormRef?.resetFields()"
    >
      <el-form
        ref="passwordFormRef"
        :model="passwordForm"
        :rules="passwordRules"
        label-position="top"
      >
        <el-form-item
          label="原密码"
          prop="originalPassword"
          ><el-input
            v-model="passwordForm.originalPassword"
            type="password"
            show-password
        /></el-form-item>
        <el-form-item
          label="新密码（8～128 位）"
          prop="newPassword"
          ><el-input
            v-model="passwordForm.newPassword"
            type="password"
            show-password
        /></el-form-item>
        <el-form-item
          label="确认新密码"
          prop="confirmPassword"
          ><el-input
            v-model="passwordForm.confirmPassword"
            type="password"
            show-password
        /></el-form-item>
      </el-form>
      <template #footer
        ><el-button @click="passwordDialogVisible = false">取消</el-button
        ><el-button
          type="primary"
          :loading="passwordSaving"
          @click="changePassword"
          >保存</el-button
        ></template
      >
    </el-dialog>
  </div>
</template>

<style scoped lang="scss" src="./AccountView.styles.scss"></style>
