/**
 * rFlows demo — client-side WebSocket controller.
 *
 * Connects via DemoSync, extracts expenses and budget from shapes,
 * renders/updates budget overview, expense list, balances, settlements,
 * category breakdown, and per-person stats. Supports inline expense editing.
 */

import { DemoSync } from "@lib/demo-sync-vanilla";
import type { DemoShape } from "@lib/demo-sync-vanilla";

// ── Types ──

interface ExpenseShape extends DemoShape {
	type: "demo-expense";
	description: string;
	amount: number;
	currency: string;
	paidBy: string;
	split: "equal" | "custom";
	category: "transport" | "accommodation" | "activity" | "food";
	date: string;
}

interface BudgetShape extends DemoShape {
	type: "folk-budget";
	budgetTitle: string;
	currency: string;
	budgetTotal: number;
	spent: number;
	categories: { name: string; budget: number; spent: number }[];
}

function isExpense(shape: DemoShape): shape is ExpenseShape {
	return shape.type === "demo-expense" && typeof (shape as ExpenseShape).amount === "number";
}

function isBudget(shape: DemoShape): shape is BudgetShape {
	return shape.type === "folk-budget";
}

// ── Constants ──

const MEMBERS = ["Maya", "Liam", "Priya", "Omar"];

const MEMBER_COLORS: Record<string, string> = {
	Maya: "#10b981",
	Liam: "#06b6d4",
	Priya: "#8b5cf6",
	Omar: "#f59e0b",
};

const MEMBER_BG: Record<string, string> = {
	Maya: "rd-bg-emerald",
	Liam: "rd-bg-cyan",
	Priya: "rd-bg-violet",
	Omar: "rd-bg-amber",
};

const CATEGORY_ICONS: Record<string, string> = {
	transport: "\u{1F682}",
	accommodation: "\u{1F3E8}",
	activity: "\u26F7",
	food: "\u{1F372}",
};

const CATEGORY_LABELS: Record<string, string> = {
	transport: "Transport",
	accommodation: "Accommodation",
	activity: "Activities",
	food: "Food & Drink",
};

const CATEGORY_TEXT_CLASS: Record<string, string> = {
	transport: "rd-cyan",
	accommodation: "rd-violet",
	activity: "rd-amber",
	food: "rd-rose",
};

// ── Helpers ──

function fmt(amount: number): string {
	return `\u20AC${amount.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 2 })}`;
}

function formatDate(dateStr: string): string {
	try {
		const d = new Date(dateStr);
		return d.toLocaleDateString("en-GB", { day: "numeric", month: "short" });
	} catch {
		return dateStr;
	}
}

function esc(s: string): string {
	return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}

// ── Balance computation ──

interface BalanceEntry {
	name: string;
	paid: number;
	owes: number;
	balance: number;
}

function computeBalances(expenses: ExpenseShape[]): BalanceEntry[] {
	const total = expenses.reduce((s, e) => s + e.amount, 0);
	const perPerson = total / MEMBERS.length;
	const paid: Record<string, number> = {};
	MEMBERS.forEach((m) => (paid[m] = 0));
	expenses.forEach((e) => (paid[e.paidBy] = (paid[e.paidBy] || 0) + e.amount));
	return MEMBERS.map((name) => ({
		name,
		paid: paid[name] || 0,
		owes: perPerson,
		balance: (paid[name] || 0) - perPerson,
	}));
}

// ── Settlement computation (greedy) ──

interface Settlement {
	from: string;
	to: string;
	amount: number;
}

function computeSettlements(balances: BalanceEntry[]): Settlement[] {
	const debtors = balances
		.filter((b) => b.balance < -0.01)
		.map((b) => ({ ...b, balance: -b.balance }));
	const creditors = balances
		.filter((b) => b.balance > 0.01)
		.map((b) => ({ ...b }));

	debtors.sort((a, b) => b.balance - a.balance);
	creditors.sort((a, b) => b.balance - a.balance);

	const settlements: Settlement[] = [];
	let di = 0,
		ci = 0;

	while (di < debtors.length && ci < creditors.length) {
		const amount = Math.min(debtors[di].balance, creditors[ci].balance);
		if (amount > 0.01) {
			settlements.push({
				from: debtors[di].name,
				to: creditors[ci].name,
				amount: Math.round(amount * 100) / 100,
			});
		}
		debtors[di].balance -= amount;
		creditors[ci].balance -= amount;
		if (debtors[di].balance < 0.01) di++;
		if (creditors[ci].balance < 0.01) ci++;
	}

	return settlements;
}

// ── DOM refs ──

const connBadge = document.getElementById("rd-conn-badge") as HTMLElement;
const resetBtn = document.getElementById("rd-reset-btn") as HTMLButtonElement;
const loadingEl = document.getElementById("rd-loading") as HTMLElement;
const emptyEl = document.getElementById("rd-empty") as HTMLElement;

const budgetSection = document.getElementById("rd-budget-section") as HTMLElement;
const expensesSection = document.getElementById("rd-expenses-section") as HTMLElement;
const spendingSection = document.getElementById("rd-spending-section") as HTMLElement;
const personSection = document.getElementById("rd-person-section") as HTMLElement;

const budgetTotalEl = document.getElementById("rd-budget-total") as HTMLElement;
const budgetSpentEl = document.getElementById("rd-budget-spent") as HTMLElement;
const budgetRemainingEl = document.getElementById("rd-budget-remaining") as HTMLElement;
const budgetRemainingLabel = document.getElementById("rd-budget-remaining-label") as HTMLElement;
const budgetPctLabel = document.getElementById("rd-budget-pct-label") as HTMLElement;
const budgetLeftLabel = document.getElementById("rd-budget-left-label") as HTMLElement;
const budgetBar = document.getElementById("rd-budget-bar") as HTMLElement;

const expenseList = document.getElementById("rd-expense-list") as HTMLElement;
const expenseCount = document.getElementById("rd-expense-count") as HTMLElement;
const expenseTotal = document.getElementById("rd-expense-total") as HTMLElement;
const balancesBody = document.getElementById("rd-balances-body") as HTMLElement;
const settlementsBody = document.getElementById("rd-settlements-body") as HTMLElement;

// ── DemoSync ──

const sync = new DemoSync({ filter: ["demo-expense", "folk-budget"] });

// Editing state
let editingExpenseId: string | null = null;

// Show loading spinner immediately
loadingEl.style.display = "";

// ── Connection events ──

sync.addEventListener("connected", () => {
	connBadge.className = "rd-status rd-status--connected";
	connBadge.textContent = "Connected";
	resetBtn.disabled = false;
});

sync.addEventListener("disconnected", () => {
	connBadge.className = "rd-status rd-status--disconnected";
	connBadge.textContent = "Disconnected";
	resetBtn.disabled = true;
});

// ── Snapshot -> render ──

sync.addEventListener("snapshot", ((e: CustomEvent) => {
	const shapes: Record<string, DemoShape> = e.detail.shapes;

	const expenses = Object.values(shapes)
		.filter(isExpense)
		.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());

	const budget = Object.values(shapes).find(isBudget) ?? null;

	// Hide loading
	loadingEl.style.display = "none";

	const hasData = expenses.length > 0 || budget !== null;

	if (!hasData) {
		emptyEl.style.display = "";
		budgetSection.style.display = "none";
		expensesSection.style.display = "none";
		spendingSection.style.display = "none";
		personSection.style.display = "none";
		return;
	}
	emptyEl.style.display = "none";

	// ── Computed values ──
	const totalSpent = expenses.reduce((s, ex) => s + ex.amount, 0);
	const budgetTotal = budget?.budgetTotal ?? 4000;
	const budgetSpent = budget?.spent ?? totalSpent;
	const budgetRemaining = budgetTotal - budgetSpent;
	const budgetPct = budgetTotal > 0 ? Math.min(100, Math.round((budgetSpent / budgetTotal) * 100)) : 0;

	const balances = computeBalances(expenses);
	const settlements = computeSettlements(balances);

	// Category breakdown from budget shape or computed from expenses
	let categoryBreakdown: { name: string; budget: number; spent: number }[];
	if (budget?.categories && budget.categories.length > 0) {
		categoryBreakdown = budget.categories;
	} else {
		const cats: Record<string, number> = {};
		expenses.forEach((ex) => (cats[ex.category] = (cats[ex.category] || 0) + ex.amount));
		categoryBreakdown = Object.entries(cats).map(([name, spent]) => ({
			name,
			budget: Math.round(budgetTotal / 4),
			spent,
		}));
	}

	// ── Update Budget Overview ──
	budgetSection.style.display = "";
	budgetTotalEl.textContent = fmt(budgetTotal);
	budgetSpentEl.textContent = fmt(budgetSpent);
	budgetRemainingEl.textContent = fmt(Math.abs(budgetRemaining));
	budgetRemainingEl.className = `rd-stat__value ${budgetRemaining >= 0 ? "rd-cyan" : "rd-rose"}`;
	budgetRemainingLabel.textContent = budgetRemaining >= 0 ? "Remaining" : "Over Budget";
	budgetPctLabel.textContent = `${budgetPct}% used`;
	budgetLeftLabel.textContent = `${fmt(Math.abs(budgetRemaining))} ${budgetRemaining >= 0 ? "left" : "over"}`;
	budgetBar.style.width = `${budgetPct}%`;
	budgetBar.className = `rd-progress__fill ${budgetPct >= 90 ? "rd-progress__fill--rose" : budgetPct >= 70 ? "rd-progress__fill--amber" : "rd-progress__fill--emerald"}`;

	// Category breakdown
	const catSection = document.getElementById("rd-category-breakdown")!;
	const catCards = catSection.querySelectorAll<HTMLElement>("[data-category]");
	catCards.forEach((card) => {
		const key = card.dataset.category!;
		const catData = categoryBreakdown.find(
			(c) => c.name.toLowerCase() === key,
		);
		const spent = catData?.spent ?? 0;
		const catBudget = catData?.budget ?? Math.round(budgetTotal / 4);
		const catPct = catBudget > 0 ? Math.min(100, Math.round((spent / catBudget) * 100)) : 0;

		const amountsEl = card.querySelector("[data-cat-amounts]") as HTMLElement;
		const barEl = card.querySelector("[data-cat-bar]") as HTMLElement;
		const pctEl = card.querySelector("[data-cat-pct]") as HTMLElement;

		if (amountsEl) amountsEl.textContent = `${fmt(spent)} / ${fmt(catBudget)}`;
		if (barEl) barEl.style.width = `${catPct}%`;
		if (pctEl) pctEl.textContent = `${catPct}% used`;
	});

	// ── Update Expenses ──
	if (expenses.length > 0) {
		expensesSection.style.display = "";
		expenseCount.textContent = `Expenses (${expenses.length})`;
		expenseTotal.textContent = fmt(totalSpent);

		expenseList.innerHTML = expenses
			.map((ex) => {
				const catIcon = CATEGORY_ICONS[ex.category] || "\u{1F4B0}";
				const catLabel = CATEGORY_LABELS[ex.category] || ex.category;
				const catTextClass = CATEGORY_TEXT_CLASS[ex.category] || "rd-text-muted";
				const perPerson = fmt(Math.round((ex.amount / MEMBERS.length) * 100) / 100);

				return `<div class="rd-item" data-expense-id="${esc(ex.id)}">
  <div style="width:2.5rem; height:2.5rem; border-radius:0.75rem; display:flex; align-items:center; justify-content:center; font-size:1.125rem; background:rgba(51,65,85,0.4); flex-shrink:0;">
    ${catIcon}
  </div>
  <div style="flex:1; min-width:0;">
    <p style="font-size:0.875rem; color:#e2e8f0; font-weight:500; margin:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${esc(ex.description)}</p>
    <div style="display:flex; align-items:center; gap:0.375rem; font-size:0.75rem; color:#64748b; margin-top:0.125rem; flex-wrap:wrap;">
      <span class="${catTextClass}">${catLabel}</span>
      <span>\u00B7</span>
      <span>Paid by ${esc(ex.paidBy)}</span>
      <span>\u00B7</span>
      <span>${ex.split === "equal" ? `Split ${MEMBERS.length} ways` : "Custom split"}</span>
      <span>\u00B7</span>
      <span>${formatDate(ex.date)}</span>
    </div>
  </div>
  <button class="rd-expense-amount" data-edit-expense="${esc(ex.id)}" data-amount="${ex.amount}" style="background:none; border:none; cursor:pointer; text-align:right; padding:0.25rem 0.5rem; border-radius:0.5rem; transition:background 0.15s; flex-shrink:0;" title="Click to edit amount">
    <span style="font-size:0.875rem; font-weight:600; color:#e2e8f0; display:block;">${fmt(ex.amount)}</span>
    <span style="font-size:0.75rem; color:#64748b; display:block;">${perPerson}/person</span>
  </button>
</div>`;
			})
			.join("");
	} else {
		expensesSection.style.display = "none";
	}

	// ── Update Balances ──
	balancesBody.innerHTML = balances
		.map((b) => {
			const bgClass = MEMBER_BG[b.name] || "rd-bg-slate";
			const initial = b.name[0];
			const balanceColor = b.balance >= 0 ? "rd-emerald" : "rd-rose";
			const balanceStr = `${b.balance >= 0 ? "+" : ""}${fmt(Math.round(b.balance * 100) / 100)}`;

			return `<div style="display:flex; align-items:center; gap:0.75rem; margin-bottom:0.75rem;">
  <div class="rd-avatar ${bgClass}" style="width:2rem; height:2rem; font-size:0.75rem;">${initial}</div>
  <div style="flex:1; min-width:0;">
    <p style="font-size:0.875rem; color:#e2e8f0; margin:0;">${esc(b.name)}</p>
    <p style="font-size:0.75rem; color:#64748b; margin:0;">Paid ${fmt(b.paid)}</p>
  </div>
  <span style="font-size:0.875rem; font-weight:600;" class="${balanceColor}">${balanceStr}</span>
</div>`;
		})
		.join("");

	// ── Update Settlements ──
	if (settlements.length > 0) {
		settlementsBody.innerHTML =
			settlements
				.map(
					(s) => `<div style="display:flex; align-items:center; gap:0.5rem; background:rgba(51,65,85,0.3); border-radius:0.75rem; padding:0.75rem; margin-bottom:0.5rem;">
  <span style="font-size:0.875rem; font-weight:500; color:#fb7185;">${esc(s.from)}</span>
  <span style="flex:1; text-align:center;">
    <span style="font-size:0.75rem; color:#64748b;">\u2192</span>
    <span style="display:block; font-size:0.875rem; font-weight:600; color:white;">${fmt(s.amount)}</span>
  </span>
  <span style="font-size:0.875rem; font-weight:500; color:#34d399;">${esc(s.to)}</span>
</div>`,
				)
				.join("") +
			`<p style="font-size:0.75rem; color:#64748b; text-align:center; margin:0.5rem 0 0;">
  ${settlements.length} payment${settlements.length !== 1 ? "s" : ""} to settle all debts
</p>`;
	} else {
		settlementsBody.innerHTML = `<p style="font-size:0.875rem; color:#64748b; text-align:center;">All settled up!</p>`;
	}

	// ── Update Spending by Category ──
	if (expenses.length > 0) {
		spendingSection.style.display = "";
		const catKeys = ["transport", "accommodation", "activity", "food"] as const;
		catKeys.forEach((cat) => {
			const catExpenses = expenses.filter((e) => e.category === cat);
			const catTotal = catExpenses.reduce((s, e) => s + e.amount, 0);
			const catPct = totalSpent > 0 ? Math.round((catTotal / totalSpent) * 100) : 0;

			const card = document.querySelector<HTMLElement>(`[data-spending-cat="${cat}"]`);
			if (!card) return;
			const amountEl = card.querySelector("[data-spending-amount]") as HTMLElement;
			const barEl = card.querySelector("[data-spending-bar]") as HTMLElement;
			const pctEl = card.querySelector("[data-spending-pct]") as HTMLElement;

			if (amountEl) amountEl.textContent = fmt(catTotal);
			if (barEl) barEl.style.width = `${catPct}%`;
			if (pctEl) pctEl.textContent = `${catPct}% of total`;
		});
	} else {
		spendingSection.style.display = "none";
	}

	// ── Update Per Person ──
	if (expenses.length > 0) {
		personSection.style.display = "";
		balances.forEach((b) => {
			const card = document.querySelector<HTMLElement>(`[data-person="${b.name}"]`);
			if (!card) return;
			const paidPct = totalSpent > 0 ? Math.round((b.paid / totalSpent) * 100) : 0;
			const paidEl = card.querySelector("[data-person-paid]") as HTMLElement;
			const pctEl = card.querySelector("[data-person-pct]") as HTMLElement;
			const balanceEl = card.querySelector("[data-person-balance]") as HTMLElement;

			if (paidEl) paidEl.textContent = fmt(b.paid);
			if (pctEl) pctEl.textContent = `paid (${paidPct}%)`;
			if (balanceEl) {
				const roundedBalance = Math.round(b.balance * 100) / 100;
				const label = b.balance >= 0 ? "Gets back " : "Owes ";
				balanceEl.textContent = `${label}${fmt(Math.abs(roundedBalance))}`;
				balanceEl.className = b.balance >= 0 ? "rd-emerald" : "rd-rose";
				balanceEl.style.fontSize = "0.875rem";
				balanceEl.style.fontWeight = "600";
				balanceEl.style.margin = "0.25rem 0 0";
			}
		});
	} else {
		personSection.style.display = "none";
	}
}) as EventListener);

// ── Inline expense editing via event delegation ──

document.addEventListener("click", (e) => {
	const target = e.target as HTMLElement;

	// Click on amount button to start editing
	const amountBtn = target.closest<HTMLElement>("[data-edit-expense]");
	if (amountBtn && !editingExpenseId) {
		const expenseId = amountBtn.dataset.editExpense!;
		const currentAmount = parseFloat(amountBtn.dataset.amount!);
		editingExpenseId = expenseId;

		amountBtn.innerHTML = `
			<div style="display:flex; align-items:center; gap:0.25rem;">
				<span style="font-size:0.875rem; color:#94a3b8;">\u20AC</span>
				<input type="number" value="${currentAmount}" step="0.01" min="0"
					style="width:5rem; background:#334155; border:1px solid rgba(16,185,129,0.5); border-radius:0.5rem; padding:0.25rem 0.5rem; font-size:0.875rem; color:white; text-align:right; outline:none;"
					id="rd-edit-input" autofocus>
				<button id="rd-edit-save" style="background:rgba(16,185,129,0.1); color:#34d399; border:none; cursor:pointer; font-size:0.75rem; padding:0.125rem 0.375rem; border-radius:0.25rem;">\u2713</button>
				<button id="rd-edit-cancel" style="background:rgba(51,65,85,0.5); color:#94a3b8; border:none; cursor:pointer; font-size:0.75rem; padding:0.125rem 0.375rem; border-radius:0.25rem;">\u2717</button>
			</div>`;

		const input = document.getElementById("rd-edit-input") as HTMLInputElement;
		if (input) {
			input.focus();
			input.select();
		}
		return;
	}

	// Save edit
	if (target.id === "rd-edit-save" || target.closest("#rd-edit-save")) {
		saveEdit();
		return;
	}

	// Cancel edit
	if (target.id === "rd-edit-cancel" || target.closest("#rd-edit-cancel")) {
		cancelEdit();
		return;
	}
});

// Handle keyboard events on edit input
document.addEventListener("keydown", (e) => {
	if (!editingExpenseId) return;
	const input = document.getElementById("rd-edit-input") as HTMLInputElement;
	if (!input || e.target !== input) return;

	if (e.key === "Enter") {
		e.preventDefault();
		saveEdit();
	} else if (e.key === "Escape") {
		e.preventDefault();
		cancelEdit();
	}
});

function saveEdit(): void {
	if (!editingExpenseId) return;
	const input = document.getElementById("rd-edit-input") as HTMLInputElement;
	if (!input) return;

	const newAmount = parseFloat(input.value);
	if (!isNaN(newAmount) && newAmount >= 0) {
		sync.updateShape(editingExpenseId, {
			amount: Math.round(newAmount * 100) / 100,
		});
	}
	editingExpenseId = null;
}

function cancelEdit(): void {
	editingExpenseId = null;
	// Re-render will happen on next snapshot; force it by dispatching current state
	sync.dispatchEvent(
		new CustomEvent("snapshot", {
			detail: { shapes: sync.shapes },
		}),
	);
}

// ── Reset button ──

resetBtn.addEventListener("click", async () => {
	resetBtn.disabled = true;
	try {
		await sync.resetDemo();
	} catch (err) {
		console.error("Reset failed:", err);
	} finally {
		if (sync.connected) resetBtn.disabled = false;
	}
});

// ── Connect ──

sync.connect();
