<template>
	<view class="todo-page page-enter theme-transition" :style="{ background: themeConfig.bgColor }">
		<PageHeader
			title="待办清单"
			subtitle="高效规划每一天"
			:showRight="true"
		>
			<template #right>
				<view
					class="header-btn"
					:style="{ background: themeConfig.cardBgColor, boxShadow: `0 4rpx 16rpx ${themeConfig.shadowLight}` }"
					@click="toggleBatchMode"
				>
					<FaIcon icon="bars" :size="20" :color="themeConfig.textPrimary" />
				</view>
			</template>
		</PageHeader>

		<view class="sticky-filters">
			<view class="filter-section card-enter">
				<view
					class="search-wrap"
					:style="{ background: themeConfig.cardBgColor, boxShadow: `0 6rpx 24rpx ${themeConfig.shadowLight}` }"
				>
					<FaIcon icon="search" :size="22" :color="themeConfig.textSecondary" />
					<input
						v-model="searchWord"
						@input="handleSearchInput"
						placeholder="搜索待办"
						:style="{ color: themeConfig.textPrimary }"
					/>
					<FaIcon
						v-if="searchWord"
						icon="xmark"
						:size="14"
						:color="themeConfig.textSecondary"
						@click="clearSearch"
					/>
				</view>

				<view class="filter-row">
					<CapsuleFilter
						:options="timeFilterOptions"
						:value="activeTimeTag"
						:color="themeConfig.primaryColor"
						size="small"
						@change="handleTimeFilterChange"
					/>
				</view>

				<view class="filter-row">
					<CapsuleFilter
						:options="statusFilterOptions"
						:value="activeStatus"
						:color="themeConfig.primaryColor"
						size="small"
						@change="activeStatus = $event"
					/>
				</view>
		</view>
		</view>

		<scroll-view class="main-content" scroll-y :show-scrollbar="false">
			<view class="calendar-section" v-if="activeTimeTag !== 'today'">
				<InlineCalendar
					:value="selectedDate"
					:markDates="markDates"
					:markType="'dot'"
					:rangeSelect="activeTimeTag === 'custom'"
					:startDate="customDateRange.start"
					:endDate="customDateRange.end"
					@change="handleDateSelect"
					@rangeChange="handleRangeChange"
				/>
			</view>

			<view class="list-section card-enter">
				<view class="list-header">
					<view class="header-left">
						<view class="accent-dot" :style="{ background: themeConfig.primaryColor }"></view>
						<text class="header-label" :style="{ color: themeConfig.textPrimary }">任务列表</text>
					</view>
					<text class="header-count" :style="{ color: themeConfig.textSecondary }">{{ filteredList.length }} 项</text>
				</view>

				<SkeletonScreen v-if="loading && !firstLoadDone" type="list" :count="5" />

				<view class="task-list" v-else>
					<SwipeableItem
						v-for="item in filteredList"
						:key="`task-${item.ID}`"
						:actions="getSwipeActions(item)"
						:disabled="batchMode"
						:index="item.ID"
						:autoClose="true"
						@action="(key) => handleSwipeAction(key, item)"
						@open="handleSwipeOpen"
					>
						<view
							class="task-card"
							:style="{ background: themeConfig.cardBgColor }"
							@longpress="enterBatch(item)"
						>
							<view class="task-left">
								<view
									class="batch-checkbox"
									v-if="batchMode"
									:class="{ checked: selectedIds.includes(item.ID) }"
									:style="{
										borderColor: selectedIds.includes(item.ID) ? themeConfig.primaryColor : themeConfig.borderColor,
										background: selectedIds.includes(item.ID) ? themeConfig.gradientSoft : 'transparent'
									}"
									@click.stop="toggleSelect(item.ID)"
								>
									<FaIcon
										v-if="selectedIds.includes(item.ID)"
										icon="check"
										:size="14"
										:color="'#fff'"
									/>
								</view>

								<view
									class="priority-badge"
									:class="getPriorityClass(item.priority)"
									:style="{ background: getPriorityBg(item.priority) }"
								>
									{{ getPriorityIcon(item.priority) }}
								</view>

								<view class="task-info">
									<view class="task-title-row">
										<text
											class="task-title"
											:class="{ done: item.done }"
											:style="{ color: item.done ? themeConfig.textDisabled : themeConfig.textPrimary }"
										>
									<FaIcon
										v-if="item.done"
										icon="check"
										:size="14"
										:color="themeConfig.secondaryColor"
									/>
											{{ item.title }}
										</text>
										<text
											class="cate-badge"
											v-if="item.category"
											:style="{ background: `${themeConfig.secondaryColor}15`, color: themeConfig.secondaryColor }"
										>
											{{ getCateLabel(item.category) }}
										</text>
									</view>

									<view class="task-meta">
										<text
											class="meta-text"
											v-if="item.start_time && item.end_time"
											:style="{ color: themeConfig.textSecondary }"
										>
											⏰ {{ item.start_time }} - {{ item.end_time }}
										</text>
										<text
											class="meta-text"
											v-if="item.remark"
											:style="{ color: themeConfig.textSecondary }"
										>
											{{ item.remark }}
										</text>
										<text class="meta-date" :style="{ color: themeConfig.textSecondary }">
											{{ formatShortDate(item.date) }}
										</text>
									</view>
								</view>
							</view>
						</view>
					</SwipeableItem>

				<EmptyState
					v-if="filteredList.length === 0 && !loading"
					type="todo"
					title="暂无待办"
					desc="点击下方按钮添加新任务"
					actionText="添加待办"
					@action="openAddSheet"
				/>
				</view>
			</view>

			<view
				class="batch-bar"
				v-if="batchMode"
				:style="{ background: themeConfig.cardBgColor, boxShadow: `0 -8rpx 32rpx ${themeConfig.shadowMedium}` }"
			>
				<text class="batch-count" :style="{ color: themeConfig.textPrimary }">已选 {{ selectedIds.length }} 项</text>
				<view class="batch-btn btn btn--primary btn--sm" @click="batchSetDone">标为完成</view>
				<view class="batch-btn danger btn btn--danger btn--sm" @click="batchDelete">删除</view>
				<view
					class="batch-btn cancel"
					:style="{ background: themeConfig.borderColor, color: themeConfig.textSecondary }"
					@click="exitBatch"
				>
					取消
				</view>
			</view>
			<view class="bottom-spacer"></view>
		</scroll-view>
	</view>
	<view class="add-button" v-if="!batchMode && !showAddSheet" @click="openAddSheet">
		<view
			class="add-btn-inner"
			:style="{ background: themeConfig.gradientVibrant, boxShadow: `0 8rpx 32rpx ${themeConfig.shadowColor}` }"
		>
			<text class="add-icon">+</text>
		</view>
	</view>


<BottomSheet
	:visible.sync="showAddSheet"
	:title="isEdit ? '编辑待办' : '新增待办'"
	:maskClosable="true"
	height="85vh"
	@close="closeAddSheet"
>
	<view class="form-group">
		<text class="form-label" :style="{ color: themeConfig.textSecondary }">待办标题</text>
		<input
			v-model="form.title"
			placeholder="请输入待办标题"
			class="form-input"
			:style="{ background: themeConfig.bgColor, color: themeConfig.textPrimary, borderColor: themeConfig.borderColor }"
		/>
	</view>

	<view class="form-row">
		<view class="form-group half">
			<text class="form-label" :style="{ color: themeConfig.textSecondary }">日期</text>
			<picker mode="date" :value="form.date" @change="e => form.date = e.detail.value">
				<view
					class="form-input picker-input"
					:style="{ background: themeConfig.bgColor, color: themeConfig.textPrimary, borderColor: themeConfig.borderColor }"
				>
					{{ formatDate(form.date) }}
				</view>
			</picker>
		</view>
		<view class="form-group half">
			<text class="form-label" :style="{ color: themeConfig.textSecondary }">优先级</text>
			<view class="priority-options">
				<view
					v-for="p in TODO_PRIORITY_LIST"
					:key="p.value"
					class="priority-option"
					:class="{ active: form.priority === p.value }"
					:style="{
						background: form.priority === p.value ? getPriorityBg(p.value) : themeConfig.borderColor,
						color: form.priority === p.value ? '#fff' : themeConfig.textSecondary
					}"
					@click="form.priority = p.value"
				>
					{{ p.label }}
				</view>
			</view>
		</view>
	</view>

	<view class="form-group">
		<text class="form-label" :style="{ color: themeConfig.textSecondary }">时间段</text>
		<view class="time-picker-row">
			<picker mode="time" :value="form.start_time" @change="e => form.start_time = formatTime(e.detail.value)">
				<view
					class="time-input"
					:style="{ background: themeConfig.bgColor, color: themeConfig.textPrimary, borderColor: themeConfig.borderColor }"
				>
					{{ form.start_time }}
				</view>
			</picker>
			<text class="time-sep" :style="{ color: themeConfig.textSecondary }">至</text>
			<picker mode="time" :value="form.end_time" @change="e => form.end_time = formatTime(e.detail.value)">
				<view
					class="time-input"
					:style="{ background: themeConfig.bgColor, color: themeConfig.textPrimary, borderColor: themeConfig.borderColor }"
				>
					{{ form.end_time }}
				</view>
			</picker>
		</view>
	</view>

	<view class="form-group">
		<text class="form-label" :style="{ color: themeConfig.textSecondary }">分类</text>
		<view class="cate-options">
			<view
				v-for="c in showCate"
				:key="c.value"
				class="cate-option"
				:class="{ active: form.category === c.value }"
				:style="{
					background: form.category === c.value ? themeConfig.gradientSoft : themeConfig.borderColor,
					color: form.category === c.value ? '#fff' : themeConfig.textSecondary
				}"
				@click="form.category = c.value"
			>
				{{ c.label }}
			</view>
		</view>
	</view>

	<view class="form-group">
		<text class="form-label" :style="{ color: themeConfig.textSecondary }">备注</text>
		<textarea
			v-model="form.remark"
			placeholder="选填备注信息"
			class="form-textarea"
			:style="{ background: themeConfig.bgColor, color: themeConfig.textPrimary, borderColor: themeConfig.borderColor }"
			auto-height
		></textarea>
	</view>

	<template #footer>
		<view class="modal-footer">
			<view
				class="btn-cancel"
				:style="{ color: themeConfig.textSecondary }"
				@click="closeAddSheet"
			>
				取消
			</view>
			<view
				class="btn-confirm"
				:style="{ background: themeConfig.gradientVibrant }"
				@click="submitTodo"
			>
				{{ isEdit ? '保存修改' : '确认添加' }}
			</view>
		</view>
	</template>
</BottomSheet>

<CenterModal
	:visible.sync="showDeleteModal"
	title="确认删除"
	:confirmColor="themeConfig.dangerColor"
	confirmText="删除"
	cancelText="取消"
	@confirm="confirmDelete"
>
	<text class="modal-body-text" :style="{ color: themeConfig.textPrimary }">
		确定要删除这条待办吗？
	</text>
</CenterModal>

<CenterModal
	:visible.sync="showCustomDateModal"
	title="选择时间范围"
	@confirm="confirmCustomDate"
>
	<view class="custom-date-body">
		<view class="form-group">
			<text class="form-label" :style="{ color: themeConfig.textSecondary }">开始日期</text>
			<picker mode="date" :value="customStartDate" @change="e => customStartDate = e.detail.value">
				<view
					class="form-input"
					:style="{ background: themeConfig.bgColor, color: themeConfig.textPrimary, borderColor: themeConfig.borderColor }"
				>
					{{ customStartDate }}
				</view>
			</picker>
		</view>
		<view class="form-group">
			<text class="form-label" :style="{ color: themeConfig.textSecondary }">结束日期</text>
			<picker mode="date" :value="customEndDate" @change="e => customEndDate = e.detail.value">
				<view
					class="form-input"
					:style="{ background: themeConfig.bgColor, color: themeConfig.textPrimary, borderColor: themeConfig.borderColor }"
				>
					{{ customEndDate }}
				</view>
			</picker>
		</view>
	</view>
</CenterModal>

<CenterModal
	:visible.sync="showBatchDeleteModal"
	title="确认批量删除"
	:confirmColor="themeConfig.dangerColor"
	confirmText="删除"
	cancelText="取消"
	@confirm="confirmBatchDelete"
>
	<text class="modal-body-text" :style="{ color: themeConfig.textPrimary }">
		确定要删除选中的 {{ selectedIds.length }} 条待办吗？
	</text>
</CenterModal>

<GrowthModal
	:visible="growthModalVisible"
	:title="growthModalTitle"
	:message="growthModalMessage"
	:icon="growthModalIcon"
	:confirm-text="growthModalConfirmText"
	@close="growthModalVisible = false"
/>
	
</template>

<script>
import {
	getTodoList,
	addTodo,
	editTodo,
	deleteTodo,
	doneTodo
} from '@/api'

import PageHeader from '@/components/PageHeader.vue'
import CapsuleFilter from '@/components/CapsuleFilter.vue'
import InlineCalendar from '@/components/InlineCalendar.vue'
import BottomSheet from '@/components/BottomSheet.vue'
import CenterModal from '@/components/CenterModal.vue'
import SwipeableItem from '@/components/SwipeableItem.vue'
import EmptyState from '@/components/EmptyState.vue'
import FaIcon from '@/components/FaIcon.vue'
import GrowthModal from '@/components/GrowthModal.vue'
import SkeletonScreen from '@/components/SkeletonScreen.vue'

import themeMixin from '@/mixins/themeMixin.js'
import growthModalMixin from '@/mixins/growthModalMixin.js'

import {
	TODO_BASE_CATE,
	TODO_PRIORITY_LIST,
	WEEK_DAYS,
	TIME_FILTER_OPTIONS,
	STATUS_FILTER_OPTIONS
} from '@/utils/constants.js'
import {
	THEME_PRESETS
} from '@/utils/theme'
import { refreshNavBarTheme } from '@/utils/themeGlobal'

export default {
	components: {
		PageHeader,
		CapsuleFilter,
		InlineCalendar,
		BottomSheet,
		CenterModal,
		SwipeableItem,
		EmptyState,
		FaIcon,
		GrowthModal,
		SkeletonScreen
	},
	mixins: [themeMixin, growthModalMixin],
	data() {
		return {
			TIME_FILTER_OPTIONS,
			STATUS_FILTER_OPTIONS,
			TODO_BASE_CATE,
			TODO_PRIORITY_LIST,
			searchWord: '',
			activeTimeTag: 'today',
			activeStatus: 'all',
			loading: false,
			firstLoadDone: false,
			todoList: [],
			selectedIds: [],
			batchMode: false,
			showCustomDateModal: false,
			customStartDate: '',
			customEndDate: '',
			customDateRange: {
				start: new Date().toISOString().split('T')[0],
				end: new Date().toISOString().split('T')[0]
			},
			weekHeaders: WEEK_DAYS,
			weekDays: [],
			monthDays: [],
			customDays: [],
			selectedDate: '',
			showAddSheet: false,
			isEdit: false,
			currentEditId: '',
			submitting: false,
			form: {
				title: '',
				remark: '',
				category: 'work',
				priority: 2,
				date: '',
				start_time: '',
				end_time: ''
			},
			customDisplayText: '自定义',
			showDeleteModal: false,
			deleteTargetId: '',
			showBatchDeleteModal: false,
			searchTimer: null
		}
	},
	computed: {
		showCate() {
			return [...this.TODO_BASE_CATE]
		},
		filteredList() {
			let list = [...this.todoList]

			list = this.filterByTimeRange(list)
			list = this.filterByStatus(list)
			list = this.filterBySearch(list)
			list = this.filterBySelectedDate(list)

			return list
		},
		markDates() {
			return this.todoList.map(t => t.date)
		},
		timeFilterOptions() {
			return this.TIME_FILTER_OPTIONS.map(opt => {
				if (opt.value === 'custom' && this.customDisplayText && this.customDisplayText !== '自定义') {
					return { ...opt, label: this.customDisplayText }
				}
				return opt
			})
		},
		statusFilterOptions() {
			return this.STATUS_FILTER_OPTIONS
		}
	},
	onLoad(options) {
		const today = new Date().toISOString().split('T')[0]
		const paramsDate = options?.date || today
		this.form.date = paramsDate
		this.customStartDate = paramsDate
		this.customEndDate = paramsDate
		this.selectedDate = paramsDate

		const now = new Date()
		const hours = String(now.getHours()).padStart(2, '0')
		const minutes = String(now.getMinutes()).padStart(2, '0')
		const defaultTime = `${hours}:${minutes}`
		this.form.start_time = defaultTime
		const endDate = new Date(now.getTime() + 30 * 60000)
		this.form.end_time = `${String(endDate.getHours()).padStart(2, '0')}:${String(endDate.getMinutes()).padStart(2, '0')}`

		this.initWeekDays()
		this.initMonthDays()

		this.getList()
	},
	onShow() {
		this.syncThemeConfig()
		refreshNavBarTheme()
	},
	onUnload() {
		if (this.searchTimer) {
			clearTimeout(this.searchTimer)
		}
	},
	onPullDownRefresh() {
		this.getList().finally(() => uni.stopPullDownRefresh())
	},
	watch: {
		activeTimeTag(newVal) {
			if (newVal === 'week') this.initWeekDays()
			else if (newVal === 'month') this.initMonthDays()
			else if (newVal === 'custom') this.initCustomDays()
			this.selectedDate = ''
			this.getList()
		},
		activeStatus() {
			this.getList()
		}
	},
	methods: {
		getSwipeActions(item) {
			const actions = []
			if (!item.done) {
				actions.push({ key: 'done', label: '完成' })
			}
			actions.push({ key: 'edit', label: '编辑' })
			actions.push({ key: 'delete', label: '删除' })
			return actions
		},
		handleSwipeAction(key, item) {
			if (key === 'done') {
				this.handleDone(item.ID)
			} else if (key === 'edit') {
				this.handleEdit(item)
			} else if (key === 'delete') {
				this.handleDelete(item.ID)
			}
		},
		handleSwipeOpen(id) {
			if (this._lastOpenId && this._lastOpenId !== id) {
				this._lastOpenId = id
			}
		},
		handleTimeFilterChange(val) {
			if (val === 'custom') {
				this.showCustomDateModal = true
			} else {
				this.activeTimeTag = val
			}
		},
		handleDateSelect(dateStr) {
			this.selectedDate = this.selectedDate === dateStr ? '' : dateStr
		},
		handleRangeChange(range) {
			if (range.start && range.end) {
				this.customDateRange = {
					start: range.start,
					end: range.end
				}
				this.customDisplayText = `${range.start.slice(5)} ~ ${range.end.slice(5)}`
				this.getList()
			}
		},
		filterByTimeRange(list) {
			const now = new Date()
			const today = now.toISOString().split('T')[0]

			const rangeMap = {
				today: { start: today, end: today },
				week: this.getWeekRange(now),
				month: this.getMonthRange(now),
				custom: {
					start: this.customDateRange.start || today,
					end: this.customDateRange.end || today
				}
			}

			const { start, end } = rangeMap[this.activeTimeTag] || rangeMap.today
			return list.filter(item => item.date >= start && item.date <= end)
		},
		filterByStatus(list) {
			const map = {
				all: () => true,
				undone: item => !item.done,
				done: item => item.done
			}
			return list.filter(map[this.activeStatus] || map.all)
		},
		filterBySearch(list) {
			if (!this.searchWord) return list
			const keyword = this.searchWord.trim().toLowerCase()
			return list.filter(item => {
				return (item.title || '').toLowerCase().includes(keyword) ||
					(item.remark || '').toLowerCase().includes(keyword)
			})
		},
		filterBySelectedDate(list) {
			if (this.selectedDate && ['week', 'month', 'custom'].includes(this.activeTimeTag)) {
				return list.filter(item => item.date === this.selectedDate)
			}
			return list
		},
		getWeekRange(date) {
			const dayOfWeek = date.getDay() || 7
			const diff = dayOfWeek - 1
			const start = new Date(date)
			start.setDate(date.getDate() - diff)
			const end = new Date(start)
			end.setDate(start.getDate() + 6)
			return {
				start: start.toISOString().split('T')[0],
				end: end.toISOString().split('T')[0]
			}
		},
		getMonthRange(date) {
			const start = new Date(date.getFullYear(), date.getMonth(), 1)
			const end = new Date(date.getFullYear(), date.getMonth() + 1, 0)
			return {
				start: start.toISOString().split('T')[0],
				end: end.toISOString().split('T')[0]
			}
		},
		initWeekDays() {
			const now = new Date()
			const days = []
			const dayOfWeek = now.getDay() || 7
			const diff = dayOfWeek - 1
			const startOfWeek = new Date(now)
			startOfWeek.setDate(now.getDate() - diff)

			for (let i = 0; i < 7; i++) {
				const d = new Date(startOfWeek)
				d.setDate(startOfWeek.getDate() + i)
				days.push(d)
			}
			this.weekDays = days
		},
		initMonthDays() {
			const now = new Date()
			const days = []
			const firstDay = new Date(now.getFullYear(), now.getMonth(), 1)
			const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0)
			const startPadding = firstDay.getDay() || 7
			const startDay = new Date(firstDay)
			startDay.setDate(firstDay.getDate() - (startPadding - 1))

			for (let i = 0; i < 42; i++) {
				const d = new Date(startDay)
				d.setDate(startDay.getDate() + i)
				days.push(d)
			}
			this.monthDays = days
		},
		initCustomDays() {
			const days = []
			const start = new Date(this.customDateRange.start)
			const end = new Date(this.customDateRange.end)
			const diff = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1

			for (let i = 0; i < Math.min(diff, 35); i++) {
				const d = new Date(start)
				d.setDate(start.getDate() + i)
				days.push(d)
			}
			this.customDays = days
		},
		toggleBatchMode() {
			this.batchMode = !this.batchMode
			if (!this.batchMode) this.selectedIds = []
		},
		handleSearchInput() {
			if (this.searchTimer) {
				clearTimeout(this.searchTimer)
			}
			this.searchTimer = setTimeout(() => {
				this.getList()
			}, 300)
		},
		clearSearch() {
			this.searchWord = ''
			this.getList()
		},
		openAddSheet() {
			this.isEdit = false
			this.form = {
				title: '',
				remark: '',
				category: 'work',
				priority: 2,
				date: new Date().toISOString().split('T')[0],
				start_time: this.form.start_time,
				end_time: this.form.end_time
			}
			this.showAddSheet = true
		},
		closeAddSheet() {
			this.showAddSheet = false
		},
		openCustomDatePicker() {
			this.showCustomDateModal = true
		},
		confirmCustomDate() {
			this.customDateRange = {
				start: this.customStartDate,
				end: this.customEndDate
			}
			this.customDisplayText = `${this.customStartDate.slice(5)} ~ ${this.customEndDate.slice(5)}`
			this.activeTimeTag = 'custom'
			this.showCustomDateModal = false
		},
		enterBatch(item) {
			this.batchMode = true
			if (item && item.ID) {
				this.toggleSelect(item.ID)
			}
		},
		toggleSelect(id) {
			const index = this.selectedIds.indexOf(id)
			if (index > -1) {
				this.selectedIds.splice(index, 1)
			} else {
				this.selectedIds.push(id)
			}
		},
		async handleDone(id) {
			try {
				await doneTodo({ id, status: 1 })
				uni.showToast({ title: '已完成', icon: 'success' })
				this.getList()
			} catch (e) {
				uni.showToast({ title: '操作失败', icon: 'none' })
			}
		},
		handleEdit(item) {
			this.isEdit = true
			this.currentEditId = item.ID
			this.form = {
				title: item.title,
				remark: item.remark || '',
				category: item.category || 'work',
				priority: item.priority || 2,
				date: item.date,
				start_time: item.start_time || '',
				end_time: item.end_time || ''
			}
			this.showAddSheet = true
		},
		handleDelete(id) {
			this.deleteTargetId = id
			this.showDeleteModal = true
		},
		async confirmDelete() {
			try {
				await deleteTodo({ id: this.deleteTargetId })
				uni.showToast({ title: '已删除', icon: 'success' })
				this.showDeleteModal = false
				this.getList()
			} catch (e) {
				uni.showToast({ title: '删除失败', icon: 'none' })
			}
		},
		exitBatch() {
			this.batchMode = false
			this.selectedIds = []
		},
		async batchSetDone() {
			if (this.selectedIds.length === 0) return
			try {
				await Promise.all(
					this.selectedIds.map(id => doneTodo({ id, status: 1 }))
				)
				uni.showToast({ title: '批量完成成功', icon: 'success' })
				this.batchMode = false
				this.selectedIds = []
				this.getList()
			} catch (e) {
				uni.showToast({ title: '操作失败', icon: 'none' })
			}
		},
		batchDelete() {
			if (this.selectedIds.length === 0) return
			this.showBatchDeleteModal = true
		},
		async confirmBatchDelete() {
			try {
				await Promise.all(
					this.selectedIds.map(id => deleteTodo({ id }))
				)
				uni.showToast({ title: '批量删除成功', icon: 'success' })
				this.showBatchDeleteModal = false
				this.batchMode = false
				this.selectedIds = []
				this.getList()
			} catch (e) {
				uni.showToast({ title: '操作失败', icon: 'none' })
			}
		},
		async submitTodo() {
			if (!this.form.title.trim()) {
				uni.showToast({ title: '请输入待办标题', icon: 'none' })
				return
			}

			this.submitting = true
			try {
				if (this.isEdit) {
					await editTodo({
						id: this.currentEditId,
						...this.form
					})
					uni.showToast({ title: '修改成功', icon: 'success' })
				} else {
					await addTodo(this.form)
					uni.showToast({ title: '添加成功', icon: 'success' })
				}
				this.showAddSheet = false
				this.getList()
			} catch (e) {
				uni.showToast({ title: '操作失败', icon: 'none' })
			} finally {
				this.submitting = false
			}
		},
		async getList() {
			this.loading = true
			try {
				const res = await getTodoList({
					start_date: this.getStartDate(),
					end_date: this.getEndDate()
				})
				this.todoList = res || []
			} catch (e) {
				this.todoList = []
			} finally {
				this.loading = false
				this.firstLoadDone = true
			}
		},
		getStartDate() {
			if (this.activeTimeTag === 'today') {
				return new Date().toISOString().split('T')[0]
			}
			if (this.activeTimeTag === 'week') {
				return this.getWeekRange(new Date()).start
			}
			if (this.activeTimeTag === 'month') {
				return this.getMonthRange(new Date()).start
			}
			return this.customDateRange.start
		},
		getEndDate() {
			if (this.activeTimeTag === 'today') {
				return new Date().toISOString().split('T')[0]
			}
			if (this.activeTimeTag === 'week') {
				return this.getWeekRange(new Date()).end
			}
			if (this.activeTimeTag === 'month') {
				return this.getMonthRange(new Date()).end
			}
			return this.customDateRange.end
		},
		formatDate(dateStr) {
			const parts = dateStr.split('-')
			return `${parts[1]}月${parts[2]}日`
		},
		formatShortDate(dateStr) {
			const parts = dateStr.split('-')
			return `${parts[1]}/${parts[2]}`
		},
		formatTime(time) {
			return time.slice(0, 5)
		},
		getCateLabel(cateValue) {
			const cate = this.TODO_BASE_CATE.find(c => c.value === cateValue)
			return cate ? cate.label : cateValue
		},
		getPriorityClass(priority) {
			const map = { 1: 'high', 2: 'middle', 3: 'low' }
			return map[priority] || 'middle'
		},
		getPriorityIcon(priority) {
			const map = { 1: '!', 2: '•', 3: '○' }
			return map[priority] || '•'
		},
		getPriorityBg(priority) {
			const map = {
				1: this.themeConfig.dangerColor,
				2: this.themeConfig.warningColor,
				3: this.themeConfig.successColor
			}
			return map[priority] || this.themeConfig.warningColor
		}
	}
}
</script>

<style scoped>
.todo-page {
	height: 100vh;
	position: relative;
	overflow: hidden;
	display: flex;
	flex-direction: column;
}

.sticky-filters {
	flex-shrink: 0;
	z-index: 10;
}

.main-content {
	flex: 1;
	height: 0;
	overflow-y: auto;
}

.bottom-spacer {
	height: 140rpx;
	flex-shrink: 0;
}.header-btn {
	width: 48rpx;
	height: 48rpx;
	border-radius: 12rpx;
	display: flex;
	align-items: center;
	justify-content: center;
	transition: all 0.25s ease;
}

.header-btn:active {
	transform: scale(0.92);
}

.filter-section {
	padding: 0 32rpx;
	padding-top: 12rpx;
	margin-bottom: 20rpx;
}

.search-wrap {
	display: flex;
	align-items: center;
	gap: 12rpx;
	padding: 14rpx 18rpx;
	border-radius: 12rpx;
	margin-bottom: 16rpx;
}

.search-wrap input {
	flex: 1;
	font-size: 26rpx;
}

.filter-row {
	margin-bottom: 12rpx;
}

.calendar-section {
	padding: 0 32rpx;
	margin-bottom: 20rpx;
}

.list-section {
	padding: 0 32rpx;
}

.list-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	margin-bottom: 16rpx;
}

.header-left {
	display: flex;
	align-items: center;
	gap: 10rpx;
}

.accent-dot {
	width: 8rpx;
	height: 28rpx;
	border-radius: 12rpx;
}

.header-label {
	font-size: 28rpx;
	font-weight: 700;
}

.header-count {
	font-size: 24rpx;
}

.skeleton-wrap {
	display: flex;
	flex-direction: column;
	gap: 16rpx;
}

.skeleton-item {
	display: flex;
	align-items: center;
	gap: 16rpx;
	padding: 20rpx;
	background: rgba(0, 0, 0, 0.03);
	border-radius: 12rpx;
}

.skeleton-icon {
	width: 40rpx;
	height: 40rpx;
	background: rgba(0, 0, 0, 0.06);
	border-radius: 10rpx;
	animation: skeletonPulse 1.5s ease-in-out infinite;
}

.skeleton-texts {
	flex: 1;
	display: flex;
	flex-direction: column;
	gap: 10rpx;
}

.skeleton-line {
	height: 24rpx;
	background: rgba(0, 0, 0, 0.06);
	border-radius: 6rpx;
	animation: skeletonPulse 1.5s ease-in-out infinite;
}

.skeleton-line.short {
	width: 60%;
}

@keyframes skeletonPulse {
	0%, 100% { opacity: 1; }
	50% { opacity: 0.5; }
}

@keyframes pageFadeIn {
	0% { opacity: 0; transform: translateY(20rpx); }
	100% { opacity: 1; transform: translateY(0); }
}

@keyframes cardEnter {
	0% { opacity: 0; transform: translateY(16rpx); }
	100% { opacity: 1; transform: translateY(0); }
}

.content-fade {
	transition: opacity 0.25s ease;
}

.theme-transition {
	transition: background-color 0.3s ease,
	            color 0.3s ease,
	            border-color 0.3s ease,
	            box-shadow 0.3s ease,
	            opacity 0.25s ease,
	            transform 0.25s ease;
}

.task-list {
	display: flex;
	flex-direction: column;
	gap: 12rpx;
}

.task-card {
	display: flex;
	align-items: center;
	padding: 20rpx;
	border-radius: 12rpx;
	position: relative;
	z-index: 2;
	transition: transform 0.3s ease;
}

.task-left {
	display: flex;
	align-items: center;
	gap: 14rpx;
	flex: 1;
}

.batch-checkbox {
	width: 40rpx;
	height: 40rpx;
	border-radius: 10rpx;
	border: 2rpx solid;
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	transition: all 0.25s ease;
}

.priority-badge {
	width: 40rpx;
	height: 40rpx;
	border-radius: 10rpx;
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	color: #fff;
	font-size: 20rpx;
	font-weight: 700;
}

.task-info {
	flex: 1;
	min-width: 0;
}

.task-title-row {
	display: flex;
	align-items: center;
	gap: 10rpx;
	margin-bottom: 6rpx;
}

.task-title {
	font-size: 28rpx;
	font-weight: 500;
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.task-title.done {
	text-decoration: line-through;
	text-decoration-color: currentColor;
}

.cate-badge {
	font-size: 20rpx;
	padding: 4rpx 10rpx;
	border-radius: 6rpx;
	font-weight: 500;
	flex-shrink: 0;
}

.task-meta {
	display: flex;
	align-items: center;
	gap: 12rpx;
	flex-wrap: wrap;
}

.meta-text {
	font-size: 22rpx;
}

.meta-date {
	font-size: 22rpx;
	margin-left: auto;
}

.batch-bar {
	position: fixed;
	bottom: 0;
	left: 0;
	right: 0;
	display: flex;
	align-items: center;
	justify-content: space-around;
	padding: 20rpx 28rpx;
	padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
	padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
	z-index: 100;
}

.batch-count {
	font-size: 26rpx;
	font-weight: 600;
}

.batch-btn {
	padding: 12rpx 24rpx;
}

.add-button {
	position: fixed;
	border-radius: 50%;
	bottom: calc(40rpx + constant(safe-area-inset-bottom));
	bottom: calc(40rpx + env(safe-area-inset-bottom));
	right: 30rpx;
	z-index: 50;
}

.add-btn-inner {
	width: 100rpx;
	height: 100rpx;
	border-radius: 50%;
	display: flex;
	align-items: center;
	justify-content: center;
	transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.add-btn-inner:active {
	transform: scale(0.9);
}

.add-icon {
	font-size: 48rpx;
	color: #fff;
	font-weight: 500;
}

.form-group {
	margin-bottom: 20rpx;
}

.form-group.half {
	flex: 1;
}

.form-row {
	display: flex;
	gap: 16rpx;
}

.form-label {
	font-size: 24rpx;
	font-weight: 500;
	display: block;
	margin-bottom: 10rpx;
}

.form-input {
	width: 100%;
	height: 80rpx;
	padding: 0 20rpx;
	border-radius: 12rpx;
	border: 2rpx solid;
	font-size: 28rpx;
}

.picker-input {
	display: flex;
	align-items: center;
}

.time-picker-row {
	display: flex;
	align-items: center;
	gap: 16rpx;
}

.time-input {
	flex: 1;
	height: 80rpx;
	padding: 0 20rpx;
	border-radius: 12rpx;
	border: 2rpx solid;
	font-size: 28rpx;
	display: flex;
	align-items: center;
}

.time-sep {
	font-size: 28rpx;
}

.priority-options,
.cate-options {
	display: flex;
	gap: 10rpx;
	flex-wrap: wrap;
}

.priority-option,
.cate-option {
	padding: 10rpx 20rpx;
	border-radius: 12rpx;
	font-size: 24rpx;
	font-weight: 500;
	transition: all 0.25s ease;
}

.priority-option:active,
.cate-option:active {
	transform: scale(0.95);
}

.form-textarea {
	width: 100%;
	min-height: 160rpx;
	padding: 16rpx 20rpx;
	border-radius: 12rpx;
	border: 2rpx solid;
	font-size: 28rpx;
}

.modal-footer {
	display: flex;
	gap: 20rpx;
}

.btn-cancel {
	flex: 1;
	height: 96rpx;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: 12rpx;
	font-size: 30rpx;
	font-weight: 600;
	background: rgba(0, 0, 0, 0.06);
	transition: all 0.25s ease;
}

.btn-cancel:active {
	transform: scale(0.97);
	background: rgba(0, 0, 0, 0.1);
}

.btn-confirm {
	flex: 2;
	height: 96rpx;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: 12rpx;
	font-size: 30rpx;
	font-weight: 600;
	color: #fff;
	box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
	transition: all 0.25s ease;
}

.btn-confirm:active {
	transform: scale(0.97);
}

.modal-body-text {
	font-size: 28rpx;
	line-height: 1.6;
	text-align: center;
	display: block;
}

.custom-date-body {
	padding: 0 10rpx;
}
</style>
