<script lang="ts">
import { page } from '$app/state';
import { enhance } from '$app/forms';
import { onMount } from 'svelte';
import { famStore } from '$lib/stores/fam.svelte';
import { memberApi } from '$lib/client/api';
import { ViewHeader, CardGrid, Card, Button } from '$lib/components';
import type { AssignedChore, Completion, ChoreTemplate, BonusConfig, Reward } from '$lib/types';

	interface Notification {
		id: string
		famId: string
		memberId: string
		message: string
		read: boolean
		created: string
	}

	let { data } = $props();

	let role = $state(data.role || 'child')
	let famSlug = $derived(page.params.fam)
	let username = $derived(page.params.username)

	// ─── Parent View (admin overview) ───
	let summary = $state(data.summary)
	let today = $state(new Date().toISOString().slice(0, 10))

	const responseTags = ['👏 well done', '😊 really pleased', '🎯 you deserved that', '💪 great effort', '🙏 thanks']
	let selectedMessage = $state<Record<string, string>>({})
	let customMessage = $state<Record<string, string>>({})
	let toast = $state('')

	let parentMembers = $derived(famStore.initialized ? famStore.members : (data.members || []))
	let parentTemplates = $derived(famStore.initialized ? famStore.templates : (data.templates || []))
	let parentAssigned = $derived(famStore.initialized ? famStore.assigned : (data.assigned || []))
	let parentRewards = $derived(famStore.initialized ? famStore.rewards : (data.rewards || []))
	let parentCompletions = $derived(famStore.initialized ? famStore.completions : (data.completions || []))
	let parentBonusConfigs = $derived(famStore.initialized ? famStore.bonusConfigs : (data.bonusConfigs || []))

	function totalChoresFor(memberId: string): number {
		return parentAssigned.filter((a: any) => a.memberId === memberId).length
	}

	function todayCompletionsFor(memberId: string) {
		return parentCompletions.filter((c: any) => c.memberId === memberId && (c.date?.slice(0, 10) || c.date) === today)
	}

	function choreNameFor(assignedChoreId: string): string {
		const a = parentAssigned.find((x: any) => x.id === assignedChoreId)
		if (!a) return '?'
		const t = parentTemplates.find((x: any) => x.id === a.templateId)
		return a.customName || t?.name || '?'
	}

	function memberInSummary(memberId: string) {
		return summary?.summaries?.find((s: any) => s.memberId === memberId)
	}

	function claimableRewards() {
		return parentRewards.filter((r: any) => !r.claimed)
	}

	function memberClaimable(memberId: string) {
		return claimableRewards().filter((r: any) => r.memberId === memberId)
	}

	let _confetti: any;
	async function fire() {
		if (!_confetti) _confetti = await import('@hiseb/confetti').then(m => m.default);
		_confetti({ count: 80, size: 4, velocity: 500, fade: true, position: { x: window.innerWidth / 2, y: 0 } });
	}

	function showToast(msg: string) {
		toast = msg;
		setTimeout(() => toast = '', 4000);
	}

	function handleResult({ result }, onSuccess = null) {
		const d = result.data || {};
		if (d.error) showToast(d.error);
		else if (result.type === 'success') {
			if (onSuccess) onSuccess(d);
			fire();
		}
	}

	function rewardLabel(r: any): string {
		if (r.rewardType === 'cash') return `£${(Number(r.value) / 100).toFixed(2)}`
		if (r.rewardType === 'points') return `${r.value} pts`
		return r.label
	}

	function manualConfigs() {
		return parentBonusConfigs.filter((b: any) => (b.phase === 'active' || (!b.phase && b.status === 'active')) && b.type === 'manual')
	}

	// ─── Child View (kanban) ───
	let memberId = $state(data.memberId || '')
	let deviceToken = $state(data.token || '')
	let famId = $state(data.famId || '')
	let memberName = $state(data.memberName || '')
	let memberColor = $state(data.memberColor || '#6366f1')
	let editingName = $state(false)
	let nameInput = $state('')
	let showColorPicker = $state(false)

	let templates = $state(data.templates as ChoreTemplate[])
	let assigned = $state(data.assigned as AssignedChore[])
	let completions = $state(data.completions as Completion[])
	let rewards = $state(data.rewards as Reward[])
	let bonusConfigs = $state(data.bonusConfigs as BonusConfig[])

	let loading = $state(true)
	let error = $state('')

	let dailyPending = $state<AssignedChore[]>([])
	let weeklyPending = $state<AssignedChore[]>([])
	let completedToday = $state<Completion[]>([])
	let claimableRewardsChild = $state<Reward[]>([])
	let requestedRewards = $state<string[]>([])
	let claimError = $state('')
	let todayChild = $state(new Date().toISOString().slice(0, 10))
	let togglingIds = $state<string>('')

	let bonusNotices = $state<Reward[]>([])
	let totalCash = $state(0)
	let pendingCash = $state(0)
	let totalPoints = $state(0)
	let weeklyTotal = $state(0)
	let weeklyDone = $state(0)
	let bonusProgresses = $state<{ config: BonusConfig; current: number; criteria: number; periodStart: string; periodEnd: string }[]>([])

	function weekStartStr(): string {
		const d = new Date()
		const day = d.getDay()
		const diff = d.getDate() - day + (day === 0 ? -6 : 1)
		d.setDate(diff)
		return d.toISOString().slice(0, 10)
	}

	function monthStartStr(): string {
		const d = new Date()
		return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-01`
	}

	onMount(async () => {
		if (role === 'parent') return;

		if (!deviceToken) {
			deviceToken = localStorage.getItem('deviceToken') || ''
		} else {
			localStorage.setItem('deviceToken', deviceToken)
		}

		if (!deviceToken) {
			error = 'No device token found. Use the link from your invite.'
			loading = false
			return
		}

		if (data.verified && data.famId && data.memberId) {
			memberId = data.memberId
			famId = data.famId
			loading = false
		} else {
			error = 'Invalid device token. Use the link from your invite.'
			loading = false
			return
		}

		await loadAndDismiss()
	})

	async function loadAndDismiss() {
		if (!deviceToken || !famId) return
		try {
			const res = await fetch('/api/members/notifications', {
				headers: {
					'x-device-token': deviceToken,
					'x-device-famid': famId,
				},
			})
			if (!res.ok) return
			const all: Notification[] = await res.json()
			for (const n of all.filter(n => !n.read)) {
				await fetch(`/api/members/notifications/${n.id}/dismiss`, {
					method: 'POST',
					headers: {
						'x-device-token': deviceToken,
						'x-device-famid': famId,
					},
				})
			}
		} catch {}
	}

	$effect(() => {
		if (role === 'parent' || !memberId) return
		const memberChores = assigned.filter(a => a.memberId === memberId)
		dailyPending = memberChores.filter(a => a.frequency === 'daily' && !isCompleted(a.id, todayChild))
		weeklyPending = memberChores.filter(a => a.frequency === 'weekly' && !isCompleted(a.id, todayChild))
		completedToday = completions.filter(c => c.memberId === memberId && c.date?.slice(0, 10) === todayChild)
		claimableRewardsChild = rewards.filter(r =>
			r.memberId === memberId && !r.claimed
		)

		const ws = weekStartStr()
		const dailyChores = memberChores.filter(a => a.frequency === 'daily')
		const weeklyChores = memberChores.filter(a => a.frequency === 'weekly')
		weeklyTotal = dailyChores.length * 7 + weeklyChores.length
		weeklyDone = completions.filter(
			c => c.memberId === memberId && (c.date?.slice(0, 10) || c.date) >= ws
		).length

		const myRewards = rewards.filter(r => r.memberId === memberId)
		const myCompletions = completions.filter(c => c.memberId === memberId)

		bonusNotices = myRewards.filter(r => r.claimed && r.claimedAt?.slice(0, 10) === r.date)

		totalCash = myCompletions.reduce((sum, c) => {
			const chore = assigned.find(a => a.id === c.assignedChoreId)
			return sum + (chore?.type === 'money' ? Number(chore.value) : 0)
		}, 0) + myRewards.filter(r => r.rewardType === 'cash').reduce((sum, r) => sum + Number(r.value), 0)

		pendingCash = myRewards.filter(r => r.rewardType === 'cash' && !r.claimed).reduce((sum, r) => sum + Number(r.value), 0)

		totalPoints = myCompletions.reduce((sum, c) => {
			const chore = assigned.find(a => a.id === c.assignedChoreId)
			return sum + (chore?.type === 'points' ? Number(chore.value) : 0)
		}, 0) + myRewards.filter(r => r.rewardType === 'points').reduce((sum, r) => sum + Number(r.value), 0)

		const activeConfigs = bonusConfigs.filter(
			(b: BonusConfig) => (b.phase === 'active' || (!b.phase && b.status === 'active')) && b.target === 'individual' && (!b.memberId || b.memberId === memberId)
		)
		bonusProgresses = activeConfigs.map(cfg => {
			const pStart = cfg.period === 'weekly' ? ws : cfg.period === 'monthly' ? monthStartStr() : ''
			const pEnd = pStart ? (() => {
				if (cfg.period === 'weekly') {
					const d = new Date(pStart)
					d.setDate(d.getDate() + 6)
					return d.toISOString().slice(0, 10)
				}
				if (cfg.period === 'monthly') {
					const [y, m] = pStart.split('-').map(Number)
					const lastDay = new Date(y, m, 0).getDate()
					return `${pStart.slice(0, 7)}-${String(lastDay).padStart(2, '0')}`
				}
				return ''
			})() : ''

			const periodCompletions = pStart
				? myCompletions.filter(c => (c.date?.slice(0, 10) || c.date) >= pStart && (c.date?.slice(0, 10) || c.date) <= pEnd)
				: myCompletions

			let current = 0
			if (cfg.type === 'threshold') {
				current = periodCompletions.reduce((sum, c) => {
					const chore = assigned.find(a => a.id === c.assignedChoreId)
					return sum + (chore?.type === 'points' ? Number(chore.value) : 0)
				}, 0)
			} else if (cfg.type === 'count') {
				current = periodCompletions.length
			}

			return { config: cfg, current, criteria: cfg.criteriaValue || 0, periodStart: pStart, periodEnd: pEnd }
		})
	})

	function isCompleted(assignedChoreId: string, date: string): boolean {
		return completions.some(c => c.assignedChoreId === assignedChoreId && c.date?.slice(0, 10) === date)
	}

	function findCompletion(assignedChoreId: string) {
		const match = (c: Completion) => c.assignedChoreId === assignedChoreId && (c.date?.slice(0, 10) || c.date) === todayChild
		const optimistic = completions.find(c => match(c) && c.id.startsWith('optimistic-'))
		return optimistic || completions.find(match)
	}

	async function toggle(chore: AssignedChore) {
		if (togglingIds) return
		togglingIds = chore.id

		const wasCompleted = isCompleted(chore.id, todayChild)
		if (wasCompleted) {
			const existing = findCompletion(chore.id)
			if (existing) {
				const idx = completions.indexOf(existing)
				completions = [...completions.slice(0, idx), ...completions.slice(idx + 1)]
			}
		} else {
			completions = [...completions, {
				id: 'optimistic-' + chore.id,
				famId,
				memberId,
				assignedChoreId: chore.id,
				date: todayChild,
				completedAt: new Date().toISOString(),
			} as Completion]
		}

		try {
			await memberApi.toggleCompletion(deviceToken, famId, chore.id, todayChild)
			completions = completions.filter(c =>
				c.assignedChoreId !== chore.id || (c.date?.slice(0, 10) || c.date) !== todayChild || c.id.startsWith('optimistic-')
			)
		} catch (e) {
			if (wasCompleted) {
				completions = [...completions, {
					id: 'revert-' + chore.id + '-' + Date.now(),
					famId,
					memberId,
					assignedChoreId: chore.id,
					date: todayChild,
					completedAt: new Date().toISOString(),
				} as Completion]
			} else {
				const existing = findCompletion(chore.id)
				if (existing && existing.id.startsWith('optimistic-')) {
					const idx = completions.indexOf(existing)
					completions = [...completions.slice(0, idx), ...completions.slice(idx + 1)]
				}
			}
			console.error('Toggle failed:', e)
		} finally {
			togglingIds = ''
		}
	}

	function templateFor(chore: AssignedChore): ChoreTemplate | undefined {
		return templates.find(t => t.id === chore.templateId)
	}

	function choreName(chore: AssignedChore): string {
		return chore.customName || templateFor(chore)?.name || 'Chore'
	}

	function rewardLabelChild(r: Reward): string {
		if (r.rewardType === 'cash') return `£${(Number(r.value) / 100).toFixed(2)}`
		if (r.rewardType === 'points') return `${r.value} pts`
		return r.label
	}
</script>

{#if role === 'parent'}
	<!-- Parent View: Admin Overview -->
	<ViewHeader title={famStore.fam?.name || 'Dashboard'} subtitle={summary?.weekStart ? `Week of ${summary.weekStart}` : ''} />

	{#if toast}
		<div class="toast">{toast}</div>
	{/if}

	<CardGrid>
		<Card cols={1} title="Members">
			{#each parentMembers as m}
				{@const total = totalChoresFor(m.id)}
				{@const todays = todayCompletionsFor(m.id)}
				{@const done = todays.length}
				{@const pct = total > 0 ? Math.round((done / total) * 100) : 0}
				{@const s = memberInSummary(m.id)}
				<div class="member-card">
					<div class="card-header">
						<span class="dot" style="background:{m.color}"></span>
						<span class="member-name">{m.name}</span>
						<a href="/{famSlug}/{m.name}" class="link">Kanban</a>
					</div>
					<div class="stats">
						<span>Points: {s?.pointsEarned ?? 0}</span>
						<span>Money: £{(s?.moneyEarned ?? 0).toFixed(2)}</span>
					</div>
					<div class="progress-row">
						<span class="label">Today:</span>
						<div class="bar-wrap">
							<div class="bar-fill" style="width:{pct}%"></div>
						</div>
						<span class="count">{done}/{total}</span>
					</div>
					{#if done > 0}
						<ul class="done-list">
							{#each todays as c}
								<li>
									✅ {choreNameFor(c.assignedChoreId)}
									<form method="POST" action="?/revoke" use:enhance={() => { return async (args) => handleResult(args, (d) => { if (d.revoked) famStore.applyRecord('completions', { id: d.id }, 'delete'); }); }} class="revoke-form">
										<input type="hidden" name="id" value={c.id} />
										<button type="submit" class="revoke-btn" title="Revoke">↩</button>
									</form>
								</li>
							{/each}
						</ul>
					{/if}
				</div>
			{/each}
		</Card>

		<Card cols={1} title="Triggers">
			{#if manualConfigs().length === 0}
				<p class="empty">No manual bonus configs.</p>
			{:else}
				{#each manualConfigs() as bc}
					{@const val = bc.rewardType === 'cash' ? `£${(Number(bc.rewardValue) / 100).toFixed(2)}` : bc.rewardType === 'points' ? `${bc.rewardValue} pts` : bc.rewardValue}
					{@const targeted = bc.memberId ? parentMembers.find((m: any) => m.id === bc.memberId) : null}
					<div class="trigger-card">
						<div class="trigger-head">
							<span class="trigger-name">{bc.name}</span>
							<span class="trigger-value">{val}</span>
						</div>
						{#if targeted}
							<form method="POST" action="?/trigger" use:enhance={() => { return async (args: any) => handleResult(args, (d: any) => { if (d.records) { d.records.forEach((r: any) => famStore.applyRecord('rewards', r, 'create')); } }); }}>
								<input type="hidden" name="configId" value={bc.id} />
								<input type="hidden" name="memberId" value={targeted.id} />
								<div class="trigger-row">
									<span class="trigger-target" style="color:{targeted.color}">■ {targeted.name}</span>
									<Button type="submit" size="sm" variant="primary">Award</Button>
								</div>
							</form>
						{:else}
							<form method="POST" action="?/trigger" use:enhance={() => { return async (args: any) => handleResult(args, (d: any) => { if (d.records) { d.records.forEach((r: any) => famStore.applyRecord('rewards', r, 'create')); } }); }}>
								<input type="hidden" name="configId" value={bc.id} />
								<div class="trigger-row">
									<select name="memberId" class="trigger-select">
										<option value="">Select member</option>
										{#each parentMembers as m}
											<option value={m.id}>{m.name}</option>
										{/each}
									</select>
									<Button type="submit" size="sm" variant="primary">Award</Button>
								</div>
							</form>
						{/if}
					</div>
				{/each}
			{/if}
		</Card>

		<Card cols={1} title="Claims" accent="#f59e0b">
			{#if claimableRewards().length === 0}
				<p class="empty">No outstanding claims</p>
			{:else}
				{#each parentMembers as m}
					{@const mc = memberClaimable(m.id)}
					{#if mc.length > 0}
						<div class="member-claims">
							<h4><span class="dot" style="background:{m.color}"></span> {m.name}</h4>
							{#each mc as r}
								<form method="POST" action="?/claim" use:enhance={() => { return async (args: any) => handleResult(args, (d: any) => { if (d.record) { famStore.applyRecord('rewards', d.record, 'update'); } }); }}>
									<input type="hidden" name="id" value={r.id} />
									<input type="hidden" name="message" value={selectedMessage[m.id] || ''} />
									<div class="payment-row">
										<span>{r.label}</span>
										<div class="payment-right">
											<span class="value">{rewardLabel(r)}</span>
											<Button type="submit" size="sm" variant="primary">Claim</Button>
										</div>
									</div>
								</form>
							{/each}
							<div class="response-area">
								<p class="respond-label">Response:</p>
								<div class="tags">
									{#each responseTags as tag}
										<button
											type="button"
											class="tag"
											class:selected={selectedMessage[m.id] === tag}
											onclick={() => selectedMessage[m.id] = selectedMessage[m.id] === tag ? '' : tag}
										>{tag}</button>
									{/each}
								</div>
								<input
									type="text"
									class="custom-msg"
									placeholder="Or type your own..."
									bind:value={customMessage[m.id]}
									oninput={() => { if (customMessage[m.id]) selectedMessage[m.id] = customMessage[m.id]; }}
								/>
							</div>
						</div>
					{/if}
				{/each}
			{/if}
		</Card>
	</CardGrid>
{:else}
	<!-- Child View: Member Kanban -->
	<ViewHeader title={memberName || username} />

	<CardGrid>
		<Card cols={3}>

	<h1>
		<span class="color-dot" style="background:{memberColor}" role="button" tabindex="0"
			onclick={e => { e.stopPropagation(); showColorPicker = !showColorPicker }}
			onkeydown={e => e.key === 'Enter' && (showColorPicker = !showColorPicker)}></span>
		{#if editingName}
			<input type="text" bind:value={nameInput} class="name-edit"
				onkeydown={async (e: KeyboardEvent) => {
					if (e.key === 'Enter') {
						editingName = false;
						if (nameInput && nameInput !== memberName) {
							const old = memberName;
							memberName = nameInput;
							try {
								const res = await fetch('/api/members/me', {
									method: 'PATCH',
									headers: { 'Content-Type': 'application/json', 'x-device-token': deviceToken, 'x-device-famid': famId },
									body: JSON.stringify({ name: nameInput }),
								});
								if (!res.ok) memberName = old;
							} catch { memberName = old; }
						}
					} else if (e.key === 'Escape') {
						editingName = false;
						nameInput = memberName;
					}
				}}
				onblur={() => { editingName = false; nameInput = memberName; }}
				autofocus />
		{:else}
			<span class="name-display" role="button" tabindex="0"
				onclick={() => { nameInput = memberName; editingName = true; }}
				onkeydown={e => e.key === 'Enter' && (nameInput = memberName, editingName = true)}>
				{memberName || username}
			</span>
		{/if}
	</h1>

	{#if showColorPicker}
		<!-- svelte-ignore a11y_no_static_element_interactions -->
		<div class="color-picker-overlay" onclick={() => showColorPicker = false}>
			<div class="color-picker-popup" onclick={(e) => e.stopPropagation()}>
				<p>Pick a color:</p>
				<div class="color-swatches">
					{#each ['#ef4444','#f97316','#eab308','#22c55e','#14b8a6','#3b82f6','#6366f1','#a855f7','#ec4899','#78716c'] as color}
						<button class="swatch" style="background:{color}" aria-label={color}
							onclick={async () => {
								const old = memberColor;
								memberColor = color;
								showColorPicker = false;
								try {
									const res = await fetch('/api/members/me', {
										method: 'PATCH',
										headers: { 'Content-Type': 'application/json', 'x-device-token': deviceToken, 'x-device-famid': famId },
										body: JSON.stringify({ color }),
									});
									if (!res.ok) memberColor = old;
								} catch { memberColor = old; }
							}}></button>
					{/each}
				</div>
			</div>
		</div>
	{/if}

	{#if loading}
		<p>Loading...</p>
	{:else if error}
		<p class="error">{error}</p>
	{:else}

	<!-- Badge Cards -->
	<div class="badges-row">
		<div class="badge-card notice">
			<span class="badge-label">Bonus Notices</span>
			<span class="badge-value">{bonusNotices.length}</span>
			{#if bonusNotices.length > 0}
				<span class="badge-sub">+{bonusNotices.reduce((s, r) => s + Number(r.value), 0)} pts</span>
			{/if}
		</div>
		<div class="badge-card cash">
			<span class="badge-label">Total Cash</span>
			<span class="badge-value">£{(totalCash / 100).toFixed(2)}</span>
		</div>
		<div class="badge-card pending">
			<span class="badge-label">Pending Cash</span>
			<span class="badge-value">£{(pendingCash / 100).toFixed(2)}</span>
		</div>
		<div class="badge-card points">
			<span class="badge-label">Total Points</span>
			<span class="badge-value">{totalPoints}</span>
		</div>
	</div>

	<!-- Weekly Progress -->
	{#if weeklyTotal > 0}
		{@const wpct = Math.round((weeklyDone / weeklyTotal) * 100)}
		<div class="week-card">
			<div class="week-head">
				<span class="week-label">This Week</span>
				<span class="week-stat">{weeklyDone}/{weeklyTotal} done</span>
			</div>
			<div class="bar-wrap lg">
				<div class="bar-fill" style="width:{wpct}%"></div>
			</div>
		</div>
	{/if}

	<!-- Bonus Progress -->
	{#if bonusProgresses.length > 0}
		<div class="bonus-grid">
			{#each bonusProgresses as bp}
				{@const pct = bp.criteria > 0 ? Math.min(100, (bp.current / bp.criteria) * 100) : 0}
				{@const hasReward = bp.periodStart ? rewards.some(r => r.memberId === memberId && r.bonusConfigId === bp.config.id && r.date >= bp.periodStart && r.date <= bp.periodEnd) : rewards.some(r => r.memberId === memberId && r.bonusConfigId === bp.config.id)}
				{@const typeClass = bp.config.type === 'threshold' ? 'type-threshold' : bp.config.type === 'count' ? 'type-count' : 'type-manual'}
				<div class="bonus-badge {typeClass}" class:achieved={hasReward}>
					<div class="bonus-head">
						<span class="bonus-name">{bp.config.name}</span>
						<span class="bonus-type">{bp.config.type}</span>
					</div>
					<div class="bonus-body">
						{#if bp.criteria > 0}
							<div class="bar-wrap">
								<div class="bar-fill" style="width:{pct}%"></div>
							</div>
							<span class="bonus-stat">{bp.current}/{bp.criteria} {bp.config.type === 'threshold' ? 'pts' : 'chores'}</span>
						{/if}
					</div>
					{#if hasReward}
						<span class="bonus-done">Reward earned 🎉</span>
					{/if}
				</div>
			{/each}
		</div>
	{/if}

	<!-- Kanban -->
	<div class="kanban">
		<div class="column">
			<h2>Daily Pending ({dailyPending.length})</h2>
			{#if dailyPending.length === 0}
				<p class="empty">All done!</p>
			{:else}
				{#each dailyPending as chore}
					<button class="chore" onclick={() => toggle(chore)} disabled={!!togglingIds}>
						<span class="checkbox">⬜</span>
						<span class="chore-name">{choreName(chore)}</span>
						<span class="chore-value">{chore.value} {chore.type}</span>
					</button>
				{/each}
			{/if}
		</div>

		<div class="column">
			<h2>Weekly Pending ({weeklyPending.length})</h2>
			{#if weeklyPending.length === 0}
				<p class="empty">All done!</p>
			{:else}
				{#each weeklyPending as chore}
					<button class="chore" onclick={() => toggle(chore)} disabled={!!togglingIds}>
						<span class="checkbox">⬜</span>
						<span class="chore-name">{choreName(chore)}</span>
						<span class="chore-value">{chore.value} {chore.type}</span>
					</button>
				{/each}
			{/if}
		</div>

		<div class="column">
			<h2>Completed Today ({completedToday.length})</h2>
			{#if completedToday.length === 0}
				<p class="empty">Nothing yet</p>
			{:else}
				{#each completedToday as c}
					{@const chore = assigned.find(a => a.id === c.assignedChoreId)}
					{#if chore}
						<button class="chore done" onclick={() => toggle(chore)} disabled={!!togglingIds}>
							<span class="checkbox">✅</span>
							<span class="chore-name">{choreName(chore)}</span>
							<span class="chore-value">{chore.value} {chore.type}</span>
						</button>
					{/if}
				{/each}
			{/if}
		</div>

		<div class="column claim-col">
			<h2>Claimable ({claimableRewardsChild.length})</h2>
			{#if claimableRewardsChild.length === 0}
				<p class="empty">Nothing to claim</p>
			{:else}
				{#each claimableRewardsChild as r}
					<div class="claim-card">
						<div class="claim-body">
							<span class="claim-label">{r.label}</span>
							<span class="claim-value">{rewardLabelChild(r)}</span>
						</div>
						<div class="claim-actions">
							<span class="badge">{r.rewardType}</span>
							<button class="request-btn" onclick={async () => { try { await memberApi.claimReward(deviceToken, famId, r.id); requestedRewards = [...requestedRewards, r.id]; } catch (e) { claimError = e instanceof Error ? e.message : 'Claim failed'; } }} disabled={requestedRewards.includes(r.id)}>
								{requestedRewards.includes(r.id) ? 'Requested' : 'Request'}
							</button>
						</div>
					</div>
				{/each}
			{/if}
		</div>
	</div>

	{/if}
		</Card>
	</CardGrid>
{/if}

<style>
	.toast { position: fixed; top: 3.5rem; right: 1.5rem; background: #dc2626; color: white; padding: 0.6rem 1rem; border-radius: 8px; font-size: 0.85rem; z-index: 999; box-shadow: 0 4px 12px rgba(0,0,0,0.15); animation: fadein 0.2s; }
	@keyframes fadein { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } }

	.empty { color: #9ca3af; font-size: 0.85rem; text-align: center; padding: 1.5rem; }

	.member-card { border: 1px solid #e5e7eb; border-radius: 8px; padding: 0.65rem; background: white; margin-bottom: 0.6rem; }
	.member-card:last-child { margin-bottom: 0; }
	.card-header { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.4rem; }
	.dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
	.member-name { font-weight: 600; flex: 1; font-size: 0.9rem; }
	.link { font-size: 0.8rem; color: #6366f1; text-decoration: none; }
	.stats { display: flex; gap: 0.75rem; font-size: 0.8rem; color: #6b7280; margin-bottom: 0.4rem; }
	.progress-row { display: flex; align-items: center; gap: 0.4rem; margin-bottom: 0.3rem; }
	.label { font-size: 0.75rem; color: #6b7280; min-width: 2.5rem; }
	.bar-wrap { flex: 1; height: 8px; background: #e5e7eb; border-radius: 4px; overflow: hidden; }
	.bar-fill { height: 100%; background: #6366f1; border-radius: 4px; transition: width 0.3s; }
	.count { font-size: 0.75rem; color: #6b7280; min-width: 3rem; text-align: right; }
	.done-list { list-style: none; padding: 0; margin: 0; }
	.done-list li { font-size: 0.75rem; padding: 0.05rem 0; color: #059669; display: flex; align-items: center; gap: 0.25rem; }
	.revoke-form { display: inline; }
	.revoke-btn { background: none; border: 1px solid transparent; border-radius: 3px; cursor: pointer; padding: 0 2px; font-size: 0.75rem; line-height: 1.2; color: #9ca3af; }
	.revoke-btn:hover { color: #dc2626; border-color: #fca5a5; background: #fef2f2; }

	.trigger-card { border: 1px solid #e5e7eb; border-radius: 8px; padding: 0.65rem; background: #f0fdf4; margin-bottom: 0.6rem; border-left: 3px solid #22c55e; }
	.trigger-card:last-child { margin-bottom: 0; }
	.trigger-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.4rem; }
	.trigger-name { font-weight: 600; font-size: 0.9rem; }
	.trigger-value { font-size: 0.85rem; color: #059669; font-weight: 600; }
	.trigger-row { display: flex; gap: 0.4rem; }
	.trigger-select { flex: 1; padding: 0.35rem; border: 1px solid #d1d5db; border-radius: 4px; font-size: 0.8rem; }

	.member-claims { margin-bottom: 0.75rem; }
	.member-claims:last-child { margin-bottom: 0; }
	.member-claims h4 { display: flex; align-items: center; gap: 0.3rem; margin: 0 0 0.2rem; font-size: 0.85rem; }
	.payment-row { display: flex; justify-content: space-between; font-size: 0.8rem; padding: 0.1rem 0; }
	.payment-row .value { font-weight: 600; color: #059669; }
	.payment-right { display: flex; align-items: center; gap: 0.4rem; }
	.respond-label { font-size: 0.8rem; color: #6b7280; margin: 0.4rem 0 0.3rem; }
	.tags { display: flex; gap: 0.3rem; flex-wrap: wrap; }
	.tag { font-size: 0.75rem; padding: 0.2rem 0.5rem; border: 1px solid #d1d5db; border-radius: 999px; background: white; cursor: pointer; }
	.tag:hover { background: #f3f4f6; }
	.tag.selected { background: #6366f1; color: white; border-color: #6366f1; }
	.custom-msg { margin-top: 0.3rem; padding: 0.3rem 0.5rem; border: 1px solid #d1d5db; border-radius: 4px; font-size: 0.8rem; width: 100%; box-sizing: border-box; }

	.error { color: #dc2626; padding: 1rem; text-align: center; }

	.badges-row { display: flex; gap: 0.75rem; margin-bottom: 1rem; flex-wrap: wrap; }
	.badge-card { flex: 1; min-width: 120px; padding: 0.75rem; border-radius: 8px; background: #f9fafb; border: 1px solid #e5e7eb; }
	.badge-label { font-size: 0.75rem; color: #6b7280; display: block; }
	.badge-value { font-size: 1.4rem; font-weight: 700; display: block; }
	.badge-sub { font-size: 0.75rem; color: #059669; }
	.badge-card.notice { border-left: 3px solid #f59e0b; }
	.badge-card.cash { border-left: 3px solid #10b981; }
	.badge-card.pending { border-left: 3px solid #3b82f6; }
	.badge-card.points { border-left: 3px solid #8b5cf6; }

	
	.week-card { background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 10px; padding: 0.85rem 1rem; margin-bottom: 1rem; }
	.week-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.5rem; }
	.week-label { font-weight: 700; font-size: 0.95rem; color: #166534; }
	.week-stat { font-size: 0.8rem; color: #16a34a; font-weight: 600; }
	.bar-wrap.lg { height: 14px; }

	.bonus-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 0.6rem; margin-bottom: 1rem; }
	.bonus-badge { border-radius: 10px; padding: 0.7rem; background: white; border: 1px solid #e5e7eb; border-left: 4px solid #9ca3af; }
	.bonus-badge.type-threshold { border-left-color: #8b5cf6; }
	.bonus-badge.type-count { border-left-color: #3b82f6; }
	.bonus-badge.type-manual { border-left-color: #f59e0b; }
	.bonus-badge.achieved { background: #f0fdf4; border-color: #bbf7d0; border-left-color: #22c55e; }
	.bonus-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.3rem; }
	.bonus-name { font-size: 0.8rem; font-weight: 600; }
	.bonus-type { font-size: 0.6rem; padding: 1px 6px; border-radius: 6px; background: #f3f4f6; color: #6b7280; text-transform: capitalize; }
	.bonus-body { display: flex; align-items: center; gap: 0.5rem; }
	.bonus-body .bar-wrap { flex: 1; height: 8px; }
	.bonus-stat { font-size: 0.7rem; color: #6b7280; white-space: nowrap; font-weight: 600; min-width: 70px; text-align: right; }
	.bonus-done { font-size: 0.7rem; color: #16a34a; font-weight: 600; margin-top: 0.2rem; display: block; }

	.kanban { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 0.75rem; margin-top: 0.5rem; }
	.column { border: 1px solid #e5e7eb; border-radius: 8px; padding: 0.75rem; background: #f9fafb; }
	.column h2 { margin: 0 0 0.75rem; font-size: 0.95rem; }

	.chore { display: flex; align-items: center; gap: 0.4rem; width: 100%; padding: 0.5rem 0.6rem; margin-bottom: 0.4rem; border: 1px solid #e5e7eb; border-radius: 6px; background: white; cursor: pointer; text-align: left; font-size: 0.85rem; }
	.chore:hover { background: #f3f4f6; }
	.chore:disabled { opacity: 0.5; cursor: not-allowed; }
	.done { opacity: 0.65; }
	.checkbox { font-size: 1rem; flex-shrink: 0; }
	.chore-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
	.chore-value { font-size: 0.75rem; color: #6b7280; font-weight: 600; white-space: nowrap; }

	.claim-col { border-left: 3px solid #10b981; }
	.claim-card { display: flex; align-items: center; gap: 0.4rem; padding: 0.5rem; margin-bottom: 0.4rem; border: 1px solid #e5e7eb; border-radius: 6px; background: white; }
	.claim-body { flex: 1; }
	.claim-label { font-size: 0.8rem; display: block; }
	.claim-value { font-size: 0.9rem; font-weight: 700; color: #059669; }
	.claim-actions { display: flex; flex-direction: column; align-items: flex-end; gap: 0.25rem; }
	.badge { font-size: 0.65rem; padding: 1px 6px; border-radius: 8px; background: #d1fae5; color: #059669; white-space: nowrap; }
	.request-btn { font-size: 0.7rem; padding: 2px 8px; background: #6366f1; color: white; border: none; border-radius: 4px; cursor: pointer; }
	.request-btn:disabled { background: #9ca3af; cursor: default; }

	h1 { display: flex; align-items: center; gap: 0.5rem; }
	.color-dot { display: inline-block; width: 18px; height: 18px; border-radius: 50%; border: 2px solid #e5e7eb; cursor: pointer; flex-shrink: 0; }
	.name-display { cursor: pointer; border-bottom: 1px dashed #d1d5db; }
	.name-edit { font-size: 1.2rem; padding: 0.1rem 0.3rem; border: 1px solid #6366f1; border-radius: 4px; outline: none; font-weight: 700; }
	.color-picker-overlay { position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.3); }
	.color-picker-popup { background: white; padding: 1.25rem; border-radius: 12px; box-shadow: 0 4px 24px rgba(0,0,0,0.15); }
	.color-picker-popup p { margin: 0 0 0.75rem; font-weight: 600; font-size: 0.9rem; }
	.color-swatches { display: flex; gap: 0.5rem; flex-wrap: wrap; }
	.swatch { width: 36px; height: 36px; border-radius: 50%; border: 2px solid #e5e7eb; cursor: pointer; }
	.swatch:hover { border-color: #6366f1; transform: scale(1.1); }
</style>
