<script setup lang="ts">
import type { LedgerEntry } from "@cents/domain";
import { ChevronDown, CircleUserRound, TrendingDown, TrendingUp, WalletCards } from "@lucide/vue";
import { computed, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import QuickEntryHost from "../components/QuickEntryHost.vue";
import { findCategory } from "../data/categories";
import { ledgerTheme } from "../data/ledgers";
import { useEntryStore } from "../stores/entries";
import { useLedgerStore } from "../stores/ledgers";

type StatsTab = "flow" | "category" | "member";
type RangeMode = "month" | "year" | "custom";

const router = useRouter();
const entryStore = useEntryStore();
const ledgerStore = useLedgerStore();
const activeTab = ref<StatsTab>("flow");
const rangeMode = ref<RangeMode>("month");
const now = new Date();
const selectedMonth = ref(`${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`);
const selectedYear = ref(now.getFullYear());
const customStart = ref(`${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-01`);
const customEnd = ref(toDateInput(now));
const currentTheme = computed(() => ledgerTheme(ledgerStore.currentLedger?.theme));

const yearOptions = computed(() => {
  const years = new Set<number>();
  for (let year = now.getFullYear() + 1; year >= now.getFullYear() - 5; year -= 1) years.add(year);
  for (const entry of entryStore.entries) years.add(new Date(entry.occurredAt).getFullYear());
  return [...years].sort((left, right) => right - left);
});

const rangeBounds = computed(() => {
  if (rangeMode.value === "month") {
    const match = /^(\d{4})-(\d{2})$/.exec(selectedMonth.value);
    if (!match) return null;
    const start = new Date(Number(match[1]), Number(match[2]) - 1, 1);
    return { start, end: new Date(start.getFullYear(), start.getMonth() + 1, 1) };
  }
  if (rangeMode.value === "year") {
    return { start: new Date(selectedYear.value, 0, 1), end: new Date(selectedYear.value + 1, 0, 1) };
  }
  const start = fromDateInput(customStart.value);
  const selectedEnd = fromDateInput(customEnd.value);
  if (!start || !selectedEnd || start > selectedEnd) return null;
  const end = new Date(selectedEnd);
  end.setDate(end.getDate() + 1);
  return { start, end };
});

const rangeLabel = computed(() => {
  if (rangeMode.value === "month") {
    const match = /^(\d{4})-(\d{2})$/.exec(selectedMonth.value);
    return match ? `${match[1]}年${Number(match[2])}月` : "所选月份";
  }
  if (rangeMode.value === "year") return `${selectedYear.value}年`;
  if (!rangeBounds.value) return "自定义日期";
  return `${formatDateLabel(customStart.value)} 至 ${formatDateLabel(customEnd.value)}`;
});

const entries = computed(() => {
  const bounds = rangeBounds.value;
  if (!bounds) return [];
  return entryStore.entries.filter((entry) => {
    const occurredAt = new Date(entry.occurredAt);
    return entry.ledgerIds.includes(ledgerStore.currentLedgerId)
      && occurredAt >= bounds.start
      && occurredAt < bounds.end;
  });
});

const totals = computed(() => summarize(entries.value));
const convertedEntries = computed(() => entries.value.filter((entry) => entry.baseAmount !== null));
const pendingConversionCount = computed(() => entries.value.length - convertedEntries.value.length);

const dailyStats = computed(() => {
  const groups = new Map<string, LedgerEntry[]>();
  for (const entry of convertedEntries.value) {
    const date = new Date(entry.occurredAt);
    const key = toDateInput(date);
    groups.set(key, [...(groups.get(key) ?? []), entry]);
  }
  return Array.from(groups, ([date, items]) => ({ date, label: dailyLabel(date), ...summarize(items) }))
    .sort((left, right) => right.date.localeCompare(left.date));
});

const maxDailyAmount = computed(() =>
  Math.max(1, ...dailyStats.value.flatMap((day) => [day.income, day.expense])),
);

const categoryStats = computed(() => {
  const groups = new Map<string, { amount: number; count: number }>();
  for (const entry of convertedEntries.value) {
    const current = groups.get(entry.categoryId) ?? { amount: 0, count: 0 };
    current.amount += entry.baseAmount!;
    current.count += 1;
    groups.set(entry.categoryId, current);
  }
  return Array.from(groups, ([categoryId, summary]) => ({
    categoryId,
    category: findCategory(categoryId),
    ...summary,
  })).sort((left, right) => right.amount - left.amount);
});

const maxCategoryAmount = computed(() => Math.max(1, ...categoryStats.value.map((item) => item.amount)));

const memberStats = computed(() => {
  const groups = new Map<string, LedgerEntry[]>();
  for (const entry of convertedEntries.value) groups.set(entry.createdBy, [...(groups.get(entry.createdBy) ?? []), entry]);
  return Array.from(groups, ([userId, items]) => ({
    userId,
    name: ledgerStore.memberName(userId),
    count: items.length,
    ...summarize(items),
  })).sort((left, right) => right.count - left.count);
});

onMounted(async () => {
  await Promise.all([entryStore.loadEntries(), ledgerStore.loadLedgers()]);
});

function summarize(items: LedgerEntry[]) {
  return items.reduce(
    (result, entry) => {
      if (entry.baseAmount === null) return result;
      if (entry.type === "income") result.income += entry.baseAmount;
      else result.expense += entry.baseAmount;
      return result;
    },
    { income: 0, expense: 0 },
  );
}

function money(value: number) {
  return (value / 100).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

function toDateInput(date: Date) {
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
}

function fromDateInput(value: string) {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return null;
  const date = new Date(`${value}T00:00:00`);
  return Number.isNaN(date.getTime()) ? null : date;
}

function formatDateLabel(value: string) {
  const date = fromDateInput(value);
  return date ? `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}` : value;
}

function dailyLabel(value: string) {
  const date = fromDateInput(value);
  if (!date) return value;
  const crossesYear = rangeBounds.value && rangeBounds.value.start.getFullYear() !== new Date(rangeBounds.value.end.getTime() - 1).getFullYear();
  return crossesYear ? `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}` : `${date.getMonth() + 1}/${date.getDate()}`;
}
</script>

<template>
  <main class="app-shell stats-shell">
    <header class="stats-header" :style="{ '--ledger-gradient': currentTheme.gradient }">
      <strong>统计</strong>
      <button type="button" @click="router.push({ path: '/ledgers', query: { returnTo: '/stats' } })">
        {{ ledgerStore.currentLedger?.name ?? "选择账本" }}
        <ChevronDown :size="15" />
      </button>
    </header>

    <div class="stats-scroll">
      <section class="stats-range" aria-label="统计时间范围">
        <div class="stats-range-modes" role="group" aria-label="时间范围类型">
          <button type="button" :class="{ active: rangeMode === 'month' }" @click="rangeMode = 'month'">月</button>
          <button type="button" :class="{ active: rangeMode === 'year' }" @click="rangeMode = 'year'">年</button>
          <button type="button" :class="{ active: rangeMode === 'custom' }" @click="rangeMode = 'custom'">自定义</button>
        </div>
        <input v-if="rangeMode === 'month'" v-model="selectedMonth" type="month" aria-label="选择月份" />
        <select v-else-if="rangeMode === 'year'" v-model="selectedYear" aria-label="选择年份">
          <option v-for="year in yearOptions" :key="year" :value="year">{{ year }}年</option>
        </select>
        <div v-else class="stats-custom-range">
          <label><span>开始</span><input v-model="customStart" type="date" aria-label="开始日期" :max="customEnd || undefined" /></label>
          <label><span>结束</span><input v-model="customEnd" type="date" aria-label="结束日期" :min="customStart || undefined" /></label>
        </div>
        <p v-if="rangeMode === 'custom' && !rangeBounds" role="alert">开始日期不能晚于结束日期</p>
      </section>

      <section class="stats-summary" :aria-label="`${rangeLabel}统计`">
        <div class="balance">
          <span>结余</span>
          <strong>¥ {{ money(totals.income - totals.expense) }}</strong>
        </div>
        <div class="income"><span><TrendingUp :size="17" />收入</span><strong>{{ money(totals.income) }}</strong></div>
        <div class="expense"><span><TrendingDown :size="17" />支出</span><strong>{{ money(totals.expense) }}</strong></div>
      </section>
      <div class="stats-conversion-slot" aria-live="polite">
        <p v-if="pendingConversionCount" class="stats-conversion-notice" role="status">
          {{ pendingConversionCount }} 条外币账目等待换算，暂未计入人民币统计
        </p>
      </div>

      <div class="stats-tabs" role="tablist" aria-label="统计维度">
        <button type="button" :class="{ active: activeTab === 'flow' }" @click="activeTab = 'flow'">收支</button>
        <button type="button" :class="{ active: activeTab === 'category' }" @click="activeTab = 'category'">分类</button>
        <button type="button" :class="{ active: activeTab === 'member' }" @click="activeTab = 'member'">记录者</button>
      </div>

      <section v-if="activeTab === 'flow'" class="stats-section" aria-label="每日收支">
        <header><strong>每日收支</strong><span>{{ entries.length }} 笔</span></header>
        <div v-for="day in dailyStats" :key="day.date" class="daily-stat-row">
          <span>{{ day.label }}</span>
          <div class="daily-bars">
            <i class="income" :style="{ width: `${(day.income / maxDailyAmount) * 100}%` }"></i>
            <i class="expense" :style="{ width: `${(day.expense / maxDailyAmount) * 100}%` }"></i>
          </div>
          <div><small class="income">+{{ money(day.income) }}</small><small class="expense">−{{ money(day.expense) }}</small></div>
        </div>
      </section>

      <section v-else-if="activeTab === 'category'" class="stats-section" aria-label="分类统计">
        <header><strong>分类金额</strong><span>{{ categoryStats.length }} 类</span></header>
        <div v-for="item in categoryStats" :key="item.categoryId" class="category-stat-row">
          <span
            class="category-stat-icon"
            :style="{ color: item.category?.color ?? '#087f72', background: item.category?.tint ?? '#e9f7f3' }"
          >
            <component :is="item.category?.icon ?? WalletCards" :size="19" />
          </span>
          <div>
            <strong>{{ item.category?.label ?? "未分类" }}</strong>
            <i :style="{ width: `${(item.amount / maxCategoryAmount) * 100}%`, background: item.category?.color ?? '#087f72' }"></i>
          </div>
          <span><strong>{{ money(item.amount) }}</strong><small>{{ item.count }} 笔</small></span>
        </div>
      </section>

      <section v-else class="stats-section" aria-label="记录者统计">
        <header><strong>记录者</strong><span>{{ memberStats.length }} 人</span></header>
        <div v-for="member in memberStats" :key="member.userId" class="member-stat-row">
          <span><CircleUserRound :size="22" /></span>
          <div><strong>{{ member.name }}</strong><small>{{ member.count }} 笔记录</small></div>
          <div><small class="income">收入 {{ money(member.income) }}</small><small class="expense">支出 {{ money(member.expense) }}</small></div>
        </div>
      </section>

      <div v-if="rangeBounds && !entries.length" class="stats-empty">{{ rangeLabel }}还没有流水</div>
    </div>

    <QuickEntryHost />
  </main>
</template>

<style scoped>
.stats-shell { background: #f5f8f7; }
.stats-header { height:74px; display:flex; align-items:center; justify-content:space-between; padding:14px 18px 8px; background:var(--ledger-gradient, #087f72); color:#fff; }
.stats-header > strong { font-size: 20px; }
.stats-header button { display: flex; align-items: center; gap: 5px; border: 0; border-radius: 7px; padding: 7px 9px; background: rgba(255,255,255,.14); color: #fff; font-size: 13px; }
.stats-scroll { position:relative; height: calc(100% - 74px); overflow-y: auto; padding: 0 16px 104px; }
.stats-range { display:grid; gap:10px; padding:12px 0; }
.stats-range-modes { display:grid; grid-template-columns:repeat(3,1fr); gap:4px; border-radius:8px; padding:4px; background:#e7eeec; }
.stats-range-modes button { height:34px; border:0; border-radius:6px; background:transparent; color:#71807c; font-size:12px; font-weight:680; }
.stats-range-modes button.active { background:#fff; color:#087f72; box-shadow:0 2px 7px rgba(28,52,47,.1); }
.stats-range > input,.stats-range > select,.stats-custom-range input { width:100%; min-width:0; height:40px; border:1px solid #d2dfdc; border-radius:7px; padding:0 10px; background:#fff; color:#344640; font-size:13px; }
.stats-custom-range { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; }
.stats-custom-range label { min-width:0; display:grid; gap:4px; }
.stats-custom-range label > span { color:#7b8884; font-size:10px; }
.stats-range > p { margin:0; color:#c74d39; font-size:11px; }
.stats-summary { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); border-top:1px solid #dce7e4; border-bottom:1px solid #dce7e4; background:#fff; }
.stats-summary > div { min-width:0; display:grid; gap:5px; padding:12px; }
.stats-summary .balance { grid-column:1 / -1; border-bottom:1px solid #e1ebe8; padding-top:15px; padding-bottom:15px; }
.stats-summary > div:last-child { border-left:1px solid #e1ebe8; }
.stats-summary span { display:flex; align-items:center; gap:5px; color:#788581; font-size:11px; }
.stats-summary strong { min-width:0; font-size:15px; line-height:1.25; overflow-wrap:anywhere; font-variant-numeric:tabular-nums; }
.stats-summary .balance strong { font-size:24px; }
.stats-summary .income { color: #0d8b67; }
.stats-summary .expense { color: #d84c36; }
.stats-conversion-slot { position:absolute; top:0; right:16px; left:16px; z-index:3; height:0; overflow:visible; pointer-events:none; }
.stats-conversion-notice { margin:0; border-left:3px solid #d69a32; padding:7px 9px; background:#fff8e9; color:#755719; font-size:11px; box-shadow:0 2px 8px rgba(58,43,17,.08); pointer-events:auto; }
.stats-tabs { display: grid; grid-template-columns: repeat(3,1fr); gap: 4px; margin: 14px 0 10px; border-radius: 8px; padding: 4px; background: #e7eeec; }
.stats-tabs button { height: 38px; border: 0; border-radius: 6px; background: transparent; color: #71807c; font-size: 13px; font-weight: 680; }
.stats-tabs button.active { background: #fff; color: #087f72; box-shadow: 0 2px 7px rgba(28,52,47,.12); }
.stats-section { border-top: 1px solid #dce7e4; border-bottom: 1px solid #dce7e4; background: #fff; }
.stats-section > header { height: 48px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #e4ece9; padding: 0 12px; }
.stats-section > header strong { font-size: 14px; }
.stats-section > header span { color: #7b8884; font-size: 11px; }
.daily-stat-row { min-height: 58px; display: grid; grid-template-columns: 34px minmax(0,1fr) 82px; align-items: center; gap: 9px; border-bottom: 1px solid #edf2f0; padding: 8px 12px; }
.daily-stat-row:last-child { border-bottom: 0; }
.daily-stat-row > span { color: #667570; font-size: 11px; }
.daily-bars { display: grid; gap: 5px; }
.daily-bars i { height: 5px; min-width: 2px; border-radius: 3px; }
.daily-bars i.income { background: #20a57d; }
.daily-bars i.expense { background: #ef6a4d; }
.daily-stat-row > div:last-child { display: grid; text-align: right; }
.daily-stat-row small { font-size: 10px; font-variant-numeric: tabular-nums; }
.income { color: #0d8b67; }
.expense { color: #d84c36; }
.category-stat-row { min-height: 64px; display: grid; grid-template-columns: 38px minmax(0,1fr) auto; align-items: center; gap: 10px; border-bottom: 1px solid #edf2f0; padding: 8px 12px; }
.category-stat-row:last-child { border-bottom: 0; }
.category-stat-icon { width: 36px; height: 36px; display: grid; place-items: center; border-radius: 8px; }
.category-stat-row > div { min-width: 0; display: grid; gap: 7px; }
.category-stat-row > div strong { font-size: 13px; }
.category-stat-row > div i { height: 4px; min-width: 2px; border-radius: 2px; }
.category-stat-row > span:last-child { display: grid; text-align: right; }
.category-stat-row > span:last-child strong { font-size: 12px; }
.category-stat-row small { color: #7d8986; font-size: 10px; }
.member-stat-row { min-height: 72px; display: grid; grid-template-columns: 42px minmax(0,1fr) auto; align-items: center; gap: 10px; padding: 10px 12px; }
.member-stat-row > span { width: 40px; height: 40px; display: grid; place-items: center; border-radius: 50%; background: #edf4ff; color: #3478e5; }
.member-stat-row > div { display: grid; }
.member-stat-row > div:last-child { text-align: right; }
.member-stat-row strong { font-size: 14px; }
.member-stat-row small { color: #7b8884; font-size: 10px; }
.member-stat-row small.income { color: #0d8b67; }
.member-stat-row small.expense { color: #d84c36; }
.stats-empty { min-height: 180px; display: grid; place-items: center; color: #7b8884; font-size: 13px; }
@media (max-width:360px) { .stats-scroll { padding-right:12px; padding-left:12px; } .stats-custom-range { grid-template-columns:1fr; } .stats-custom-range input { padding:0 8px; font-size:12px; } .stats-summary > div { padding-right:10px; padding-left:10px; } }
</style>
