/**
 * Trips module — collaborative trip planner.
 *
 * Plan trips with destinations, itinerary, bookings, expenses,
 * and packing lists. Collaborative with role-based access.
 *
 * Data layer: Automerge documents via SyncServer.
 * One document per trip: {space}:trips:trips:{tripId}
 */

import { Hono } from "hono";
import * as Automerge from "@automerge/automerge";
import { renderShell } from "../../server/shell";
import { getModuleInfoList } from "../../shared/module";
import type { RSpaceModule } from "../../shared/module";
import { verifyToken, extractToken } from "../../server/auth";
import { renderLanding } from "./landing";
import type { SyncServer } from '../../server/local-first/sync-server';
import {
	tripSchema, tripDocId,
	type TripDoc, type TripMeta, type Destination, type ItineraryItem,
	type Booking, type Expense, type PackingItem,
} from './schemas';

let _syncServer: SyncServer | null = null;

const OSRM_URL = process.env.OSRM_URL || "http://osrm-backend:5000";

// ── Helpers ──

/** Generate a short random ID (collision-safe enough for sub-collections). */
function newId(): string {
	return crypto.randomUUID().slice(0, 12);
}

/** Ensure a trip document exists; create it lazily if not. */
function ensureDoc(space: string, tripId: string): TripDoc {
	const docId = tripDocId(space, tripId);
	let doc = _syncServer!.getDoc<TripDoc>(docId);
	if (!doc) {
		doc = Automerge.change(Automerge.init<TripDoc>(), 'init', (d) => {
			const init = tripSchema.init();
			d.meta = init.meta;
			d.meta.spaceSlug = space;
			d.trip = init.trip;
			d.trip.id = tripId;
			d.destinations = {};
			d.itinerary = {};
			d.bookings = {};
			d.expenses = {};
			d.packingItems = {};
		});
		_syncServer!.setDoc(docId, doc);
	}
	return doc;
}

/** List all trip doc IDs for a given space. */
function listTripDocIds(space: string): string[] {
	const prefix = `${space}:trips:trips:`;
	return _syncServer!.listDocs().filter((id) => id.startsWith(prefix));
}

const routes = new Hono();

// ── API: Trips ──

// GET /api/trips — list trips
routes.get("/api/trips", async (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = c.get("effectiveSpace") || space;
	const docIds = listTripDocIds(dataSpace);

	const rows = docIds.map((docId) => {
		const doc = _syncServer!.getDoc<TripDoc>(docId);
		if (!doc) return null;
		const t = doc.trip;
		const destinations = Object.values(doc.destinations);
		const expenses = Object.values(doc.expenses);
		const totalSpent = expenses.reduce((sum, e) => sum + (e.amount || 0), 0);
		return {
			...t,
			destination_count: destinations.length,
			expense_count: expenses.length,
			total_spent: totalSpent,
		};
	}).filter(Boolean);

	// Sort by createdAt descending (newest first)
	rows.sort((a, b) => (b!.createdAt ?? 0) - (a!.createdAt ?? 0));
	return c.json(rows);
});

// POST /api/trips — create trip
routes.post("/api/trips", async (c) => {
	const token = extractToken(c.req.raw.headers);
	if (!token) return c.json({ error: "Authentication required" }, 401);
	let claims;
	try { claims = await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }

	const body = await c.req.json();
	const { title, description, start_date, end_date, budget_total, budget_currency } = body;
	if (!title?.trim()) return c.json({ error: "Title required" }, 400);

	const space = c.req.param("space") || "demo";
	const dataSpace = c.get("effectiveSpace") || space;
	const tripId = newId();
	const slug = title.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
	const now = Date.now();

	const docId = tripDocId(dataSpace, tripId);
	let doc = Automerge.change(Automerge.init<TripDoc>(), 'create trip', (d) => {
		const init = tripSchema.init();
		d.meta = init.meta;
		d.meta.spaceSlug = space;
		d.meta.createdAt = now;
		d.trip = {
			id: tripId,
			title: title.trim(),
			slug,
			description: description || '',
			startDate: start_date || null,
			endDate: end_date || null,
			budgetTotal: budget_total ?? null,
			budgetCurrency: budget_currency || 'USD',
			status: 'planning',
			createdBy: claims.sub,
			createdAt: now,
			updatedAt: now,
		};
		d.destinations = {};
		d.itinerary = {};
		d.bookings = {};
		d.expenses = {};
		d.packingItems = {};
	});
	_syncServer!.setDoc(docId, doc);

	return c.json(doc.trip, 201);
});

// GET /api/trips/:id — trip detail with all sub-resources
routes.get("/api/trips/:id", async (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = c.get("effectiveSpace") || space;
	const tripId = c.req.param("id");
	const docId = tripDocId(dataSpace, tripId);
	const doc = _syncServer!.getDoc<TripDoc>(docId);
	if (!doc) return c.json({ error: "Trip not found" }, 404);

	const destinations = Object.values(doc.destinations).sort((a, b) => a.sortOrder - b.sortOrder);
	const itinerary = Object.values(doc.itinerary).sort((a, b) => {
		const dateCmp = (a.date || '').localeCompare(b.date || '');
		return dateCmp !== 0 ? dateCmp : a.sortOrder - b.sortOrder;
	});
	const bookings = Object.values(doc.bookings).sort((a, b) => (a.startDate || '').localeCompare(b.startDate || ''));
	const expenses = Object.values(doc.expenses).sort((a, b) => (b.date || '').localeCompare(a.date || ''));
	const packing = Object.values(doc.packingItems).sort((a, b) => {
		const catCmp = (a.category || '').localeCompare(b.category || '');
		return catCmp !== 0 ? catCmp : a.sortOrder - b.sortOrder;
	});

	return c.json({ ...doc.trip, destinations, itinerary, bookings, expenses, packing });
});

// PUT /api/trips/:id — update trip
routes.put("/api/trips/:id", async (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = c.get("effectiveSpace") || space;
	const tripId = c.req.param("id");
	const docId = tripDocId(dataSpace, tripId);
	const doc = _syncServer!.getDoc<TripDoc>(docId);
	if (!doc) return c.json({ error: "Not found" }, 404);

	const body = await c.req.json();
	const { title, description, start_date, end_date, budget_total, budget_currency, status } = body;

	const hasFields = [title, description, start_date, end_date, budget_total, budget_currency, status]
		.some((v) => v !== undefined);
	if (!hasFields) return c.json({ error: "No fields" }, 400);

	_syncServer!.changeDoc<TripDoc>(docId, 'update trip', (d) => {
		if (title !== undefined) d.trip.title = title;
		if (description !== undefined) d.trip.description = description;
		if (start_date !== undefined) d.trip.startDate = start_date;
		if (end_date !== undefined) d.trip.endDate = end_date;
		if (budget_total !== undefined) d.trip.budgetTotal = budget_total;
		if (budget_currency !== undefined) d.trip.budgetCurrency = budget_currency;
		if (status !== undefined) d.trip.status = status;
		d.trip.updatedAt = Date.now();
	});

	const updated = _syncServer!.getDoc<TripDoc>(docId);
	return c.json(updated!.trip);
});

// ── API: Destinations ──

routes.post("/api/trips/:id/destinations", async (c) => {
	const token = extractToken(c.req.raw.headers);
	if (!token) return c.json({ error: "Authentication required" }, 401);
	try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }

	const space = c.req.param("space") || "demo";
	const dataSpace = c.get("effectiveSpace") || space;
	const tripId = c.req.param("id");
	ensureDoc(dataSpace, tripId);
	const docId = tripDocId(dataSpace, tripId);

	const body = await c.req.json();
	const destId = newId();
	const now = Date.now();

	_syncServer!.changeDoc<TripDoc>(docId, 'add destination', (d) => {
		d.destinations[destId] = {
			id: destId,
			tripId,
			name: body.name,
			country: body.country || null,
			lat: body.lat ?? null,
			lng: body.lng ?? null,
			arrivalDate: body.arrival_date || null,
			departureDate: body.departure_date || null,
			notes: body.notes || '',
			sortOrder: body.sort_order ?? 0,
			createdAt: now,
		};
	});

	const updated = _syncServer!.getDoc<TripDoc>(docId);
	return c.json(updated!.destinations[destId], 201);
});

// ── API: Itinerary ──

routes.post("/api/trips/:id/itinerary", async (c) => {
	const token = extractToken(c.req.raw.headers);
	if (!token) return c.json({ error: "Authentication required" }, 401);
	try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }

	const space = c.req.param("space") || "demo";
	const dataSpace = c.get("effectiveSpace") || space;
	const tripId = c.req.param("id");
	ensureDoc(dataSpace, tripId);
	const docId = tripDocId(dataSpace, tripId);

	const body = await c.req.json();
	const itemId = newId();
	const now = Date.now();

	_syncServer!.changeDoc<TripDoc>(docId, 'add itinerary item', (d) => {
		d.itinerary[itemId] = {
			id: itemId,
			tripId,
			destinationId: body.destination_id || null,
			title: body.title,
			category: body.category || 'ACTIVITY',
			date: body.date || null,
			startTime: body.start_time || null,
			endTime: body.end_time || null,
			notes: body.notes || '',
			sortOrder: body.sort_order ?? 0,
			createdAt: now,
		};
	});

	const updated = _syncServer!.getDoc<TripDoc>(docId);
	return c.json(updated!.itinerary[itemId], 201);
});

// ── API: Bookings ──

routes.post("/api/trips/:id/bookings", async (c) => {
	const token = extractToken(c.req.raw.headers);
	if (!token) return c.json({ error: "Authentication required" }, 401);
	try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }

	const space = c.req.param("space") || "demo";
	const dataSpace = c.get("effectiveSpace") || space;
	const tripId = c.req.param("id");
	ensureDoc(dataSpace, tripId);
	const docId = tripDocId(dataSpace, tripId);

	const body = await c.req.json();
	const bookingId = newId();
	const now = Date.now();

	_syncServer!.changeDoc<TripDoc>(docId, 'add booking', (d) => {
		d.bookings[bookingId] = {
			id: bookingId,
			tripId,
			type: body.type || 'OTHER',
			provider: body.provider || null,
			confirmationNumber: body.confirmation_number || null,
			cost: body.cost ?? null,
			currency: body.currency || 'USD',
			startDate: body.start_date || null,
			endDate: body.end_date || null,
			status: null,
			notes: body.notes || '',
			createdAt: now,
		};
	});

	const updated = _syncServer!.getDoc<TripDoc>(docId);
	return c.json(updated!.bookings[bookingId], 201);
});

// ── API: Expenses ──

routes.post("/api/trips/:id/expenses", async (c) => {
	const token = extractToken(c.req.raw.headers);
	if (!token) return c.json({ error: "Authentication required" }, 401);
	try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }

	const space = c.req.param("space") || "demo";
	const dataSpace = c.get("effectiveSpace") || space;
	const tripId = c.req.param("id");
	ensureDoc(dataSpace, tripId);
	const docId = tripDocId(dataSpace, tripId);

	const body = await c.req.json();
	const expenseId = newId();
	const now = Date.now();

	_syncServer!.changeDoc<TripDoc>(docId, 'add expense', (d) => {
		d.expenses[expenseId] = {
			id: expenseId,
			tripId,
			paidBy: null,
			description: body.description,
			amount: body.amount,
			currency: body.currency || 'USD',
			category: body.category || 'OTHER',
			date: body.date || null,
			splitType: body.split_type || 'EQUAL',
			createdAt: now,
		};
	});

	const updated = _syncServer!.getDoc<TripDoc>(docId);
	return c.json(updated!.expenses[expenseId], 201);
});

// ── API: Packing ──

routes.get("/api/trips/:id/packing", async (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = c.get("effectiveSpace") || space;
	const tripId = c.req.param("id");
	const docId = tripDocId(dataSpace, tripId);
	const doc = _syncServer!.getDoc<TripDoc>(docId);
	if (!doc) return c.json([]);

	const rows = Object.values(doc.packingItems).sort((a, b) => {
		const catCmp = (a.category || '').localeCompare(b.category || '');
		return catCmp !== 0 ? catCmp : a.sortOrder - b.sortOrder;
	});
	return c.json(rows);
});

routes.post("/api/trips/:id/packing", async (c) => {
	const token = extractToken(c.req.raw.headers);
	if (!token) return c.json({ error: "Authentication required" }, 401);
	try { await verifyToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }

	const space = c.req.param("space") || "demo";
	const dataSpace = c.get("effectiveSpace") || space;
	const tripId = c.req.param("id");
	ensureDoc(dataSpace, tripId);
	const docId = tripDocId(dataSpace, tripId);

	const body = await c.req.json();
	const itemId = newId();
	const now = Date.now();

	_syncServer!.changeDoc<TripDoc>(docId, 'add packing item', (d) => {
		d.packingItems[itemId] = {
			id: itemId,
			tripId,
			addedBy: null,
			name: body.name,
			category: body.category || 'GENERAL',
			packed: false,
			quantity: body.quantity || 1,
			sortOrder: body.sort_order ?? 0,
			createdAt: now,
		};
	});

	const updated = _syncServer!.getDoc<TripDoc>(docId);
	return c.json(updated!.packingItems[itemId], 201);
});

routes.patch("/api/packing/:id", async (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = c.get("effectiveSpace") || space;
	const packingId = c.req.param("id");

	// Find the trip doc containing this packing item
	const docIds = listTripDocIds(dataSpace);
	for (const docId of docIds) {
		const doc = _syncServer!.getDoc<TripDoc>(docId);
		if (!doc || !doc.packingItems[packingId]) continue;

		const body = await c.req.json();
		_syncServer!.changeDoc<TripDoc>(docId, 'toggle packing item', (d) => {
			d.packingItems[packingId].packed = body.packed ?? false;
		});

		const updated = _syncServer!.getDoc<TripDoc>(docId);
		return c.json(updated!.packingItems[packingId]);
	}

	return c.json({ error: "Not found" }, 404);
});

// ── Trip AI proxy endpoints ──

routes.post("/api/trips/search-flights", async (c) => {
	const KIWI_API_KEY = process.env.KIWI_API_KEY || "";
	const body = await c.req.json<{ from: string; to: string; departDate: string; returnDate?: string; passengers?: number; currency?: string }>();
	const { from, to, departDate, returnDate, passengers = 1, currency = "EUR" } = body;
	if (!from || !to || !departDate) return c.json({ error: "from, to, departDate required" }, 400);

	const dateOut = departDate.replace(/-/g, "/");
	const searchUrl = `https://www.kiwi.com/en/search/results/${encodeURIComponent(from)}/${encodeURIComponent(to)}/${dateOut}${returnDate ? "/" + returnDate.replace(/-/g, "/") : ""}/${passengers}adults`;

	if (!KIWI_API_KEY) return c.json({ flights: [], searchUrl, note: "KIWI_API_KEY not configured" });

	try {
		const params = new URLSearchParams({
			fly_from: from, fly_to: to,
			date_from: dateOut, date_to: dateOut,
			curr: currency, adults: String(passengers),
			limit: "5", sort: "price", max_stopovers: "2",
		});
		if (returnDate) { params.set("return_from", returnDate.replace(/-/g, "/")); params.set("return_to", returnDate.replace(/-/g, "/")); }

		const res = await fetch(`https://api.tequila.kiwi.com/v2/search?${params}`, { headers: { apikey: KIWI_API_KEY } });
		if (!res.ok) throw new Error(`Kiwi ${res.status}`);
		const data = await res.json();
		return c.json({ flights: (data.data || []).slice(0, 5), searchUrl });
	} catch (e: any) {
		return c.json({ flights: [], searchUrl, error: e.message }, 502);
	}
});

routes.post("/api/trips/geocode", async (c) => {
	const body = await c.req.json<{ query: string }>();
	if (!body.query) return c.json({ error: "query required" }, 400);

	try {
		const url = `https://nominatim.openstreetmap.org/search?format=json&limit=3&q=${encodeURIComponent(body.query)}`;
		const res = await fetch(url, { headers: { "User-Agent": "rTrips/1.0 (rspace.online)" } });
		if (!res.ok) throw new Error(`Nominatim ${res.status}`);
		const data = await res.json();
		return c.json(data.map((p: any) => ({ lat: parseFloat(p.lat), lng: parseFloat(p.lon), displayName: p.display_name, type: p.type })));
	} catch (e: any) {
		return c.json({ error: e.message }, 502);
	}
});

routes.post("/api/trips/nearby", async (c) => {
	const body = await c.req.json<{ lat: number; lng: number; category: string; radius?: number }>();
	const { lat, lng, category, radius = 1000 } = body;
	if (!lat || !lng || !category) return c.json({ error: "lat, lng, category required" }, 400);

	const tagMap: Record<string, string> = {
		restaurant: '["amenity"="restaurant"]', hotel: '["tourism"="hotel"]',
		cafe: '["amenity"="cafe"]', museum: '["tourism"="museum"]',
		temple: '["amenity"="place_of_worship"]', park: '["leisure"="park"]',
		attraction: '["tourism"="attraction"]',
	};
	const tag = tagMap[category] || `["amenity"="${category}"]`;

	try {
		const query = `[out:json][timeout:10];(node${tag}(around:${radius},${lat},${lng});way${tag}(around:${radius},${lat},${lng}););out center 8;`;
		const res = await fetch("https://overpass-api.de/api/interpreter", {
			method: "POST",
			body: `data=${encodeURIComponent(query)}`,
			headers: { "Content-Type": "application/x-www-form-urlencoded" },
		});
		if (!res.ok) throw new Error(`Overpass ${res.status}`);
		const data = await res.json();
		const results = (data.elements || []).filter((el: any) => el.tags?.name).slice(0, 8).map((el: any) => ({
			name: el.tags.name,
			type: el.tags.cuisine || el.tags.tourism || el.tags.amenity || category,
			lat: el.lat || el.center?.lat,
			lng: el.lon || el.center?.lon,
		}));
		return c.json({ results, category });
	} catch (e: any) {
		return c.json({ results: [], error: e.message }, 502);
	}
});

// ── OSRM proxy for route planner ──
routes.post("/api/route", async (c) => {
	const body = await c.req.json<{ start: { lng: number; lat: number }; end: { lng: number; lat: number } }>();
	const { start, end } = body;
	if (!start || !end) return c.json({ error: "start and end are required" }, 400);

	const url = `${OSRM_URL}/route/v1/driving/${start.lng},${start.lat};${end.lng},${end.lat}?geometries=geojson&overview=full`;
	try {
		const res = await fetch(url);
		const data = await res.json();
		return c.json(data, res.status as any);
	} catch (e) {
		return c.json({ error: "OSRM backend unavailable", detail: String(e) }, 502);
	}
});

// ── Route planner page ──
routes.get("/routes", (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = c.get("effectiveSpace") || space;
	return c.html(renderShell({
		title: `${space} — Route Planner | rTrips`,
		moduleId: "rtrips",
		spaceSlug: space,
		modules: getModuleInfoList(),
		theme: "dark",
		styles: `<link rel="stylesheet" href="/modules/rtrips/route-planner.css">`,
		body: `<folk-route-planner></folk-route-planner>`,
		scripts: `<script type="module" src="/modules/rtrips/folk-route-planner.js"></script>`,
	}));
});

// ── Demo dashboard ──
routes.get("/demo", (c) => {
	return c.html(renderShell({
		title: "rTrips Demo — Live Trip Dashboard | rSpace",
		moduleId: "rtrips",
		spaceSlug: "demo",
		modules: getModuleInfoList(),
		theme: "dark",
		styles: `<link rel="stylesheet" href="/modules/rtrips/trips.css">`,
		body: `
<div class="rd-page">
	<div class="rd-hero">
		<h1 id="rd-trip-title">Alpine Explorer 2026</h1>
		<p id="rd-trip-route" class="rd-hero-route">Loading route…</p>
		<div id="rd-trip-meta" class="rd-hero-meta">
			<span>📅 Jul 6–20, 2026</span>
			<span>💶 ~€4,000 budget</span>
			<span>🏔️ 3 countries</span>
		</div>
		<div id="rd-avatars" class="rd-hero-avatars"></div>
	</div>

	<div class="rd-toolbar">
		<span class="rd-status">
			<span id="rd-hero-dot" class="rd-status-dot"></span>
			<span id="rd-hero-label">Connecting…</span>
		</span>
		<button id="rd-reset-btn" class="rd-btn" disabled>Reset Demo</button>
		<a href="/rtrips" class="rd-btn" style="text-decoration:none;margin-left:auto">&larr; About rTrips</a>
	</div>

	<div class="rd-grid">
		<!-- Maps card -->
		<div class="rd-card">
			<div class="rd-card-header">
				<span class="rd-card-title">🗺️ Maps</span>
				<span id="rd-maps-live" class="rd-card-live">Live</span>
			</div>
			<div class="rd-card-body">
				<svg class="rd-map-svg" viewBox="0 0 800 300">
					<rect width="800" height="300" rx="8" fill="#0c1222"/>
					<path id="rd-route-path" d="" stroke="#14b8a6" stroke-width="2" fill="none" stroke-dasharray="6 4" opacity="0.5"/>
					<g id="rd-map-pins"></g>
				</svg>
			</div>
		</div>

		<!-- Notes / Packing card -->
		<div class="rd-card">
			<div class="rd-card-header">
				<span class="rd-card-title">📝 Packing List</span>
				<span id="rd-notes-live" class="rd-card-live">Live</span>
			</div>
			<div class="rd-card-body" id="rd-packing-list">
				<div class="rd-skeleton">
					<div class="rd-skeleton-line" style="width:80%"></div>
					<div class="rd-skeleton-line" style="width:60%"></div>
					<div class="rd-skeleton-line" style="width:70%"></div>
				</div>
			</div>
		</div>

		<!-- Calendar card -->
		<div class="rd-card">
			<div class="rd-card-header">
				<span class="rd-card-title">📅 Calendar</span>
				<span id="rd-cal-live" class="rd-card-live">Live</span>
				<span id="rd-cal-days" style="font-size:0.6875rem;color:#64748b;margin-left:auto"></span>
			</div>
			<div class="rd-card-body" id="rd-cal-grid">
				<div class="rd-skeleton">
					<div class="rd-skeleton-line" style="width:100%"></div>
					<div class="rd-skeleton-line" style="width:100%"></div>
					<div class="rd-skeleton-line" style="width:90%"></div>
				</div>
			</div>
		</div>

		<!-- Polls card -->
		<div class="rd-card">
			<div class="rd-card-header">
				<span class="rd-card-title">🗳️ Polls</span>
				<span id="rd-polls-live" class="rd-card-live">Live</span>
			</div>
			<div class="rd-card-body" id="rd-polls-body">
				<div class="rd-skeleton">
					<div class="rd-skeleton-line" style="width:75%"></div>
					<div class="rd-skeleton-line" style="width:90%"></div>
					<div class="rd-skeleton-line" style="width:60%"></div>
				</div>
			</div>
		</div>

		<!-- Funds card -->
		<div class="rd-card">
			<div class="rd-card-header">
				<span class="rd-card-title">💶 Funds</span>
				<span id="rd-funds-live" class="rd-card-live">Live</span>
				<span id="rd-funds-total" style="font-size:0.8125rem;font-weight:600;color:#14b8a6;margin-left:auto">€0</span>
			</div>
			<div class="rd-card-body">
				<div id="rd-funds-skeleton" class="rd-skeleton">
					<div class="rd-skeleton-line" style="width:85%"></div>
					<div class="rd-skeleton-line" style="width:65%"></div>
					<div class="rd-skeleton-line" style="width:70%"></div>
				</div>
				<div id="rd-funds-expenses" style="display:none"></div>
				<div id="rd-funds-balances" style="display:none"></div>
			</div>
		</div>

		<!-- Cart card -->
		<div class="rd-card">
			<div class="rd-card-header">
				<span class="rd-card-title">🛒 Cart</span>
				<span id="rd-cart-live" class="rd-card-live">Live</span>
			</div>
			<div class="rd-card-body">
				<div id="rd-cart-skeleton" class="rd-skeleton">
					<div class="rd-skeleton-line" style="width:90%"></div>
					<div class="rd-skeleton-line" style="width:75%"></div>
					<div class="rd-skeleton-line" style="width:80%"></div>
				</div>
				<div id="rd-cart-content" style="display:none"></div>
			</div>
		</div>
	</div>

	<div class="rd-footer">
		<a href="/rtrips">&larr; Back to rTrips</a>
	</div>
</div>`,
		scripts: `<script type="module" src="/modules/rtrips/trips-demo.js"></script>`,
	}));
});

// ── Page route ──
routes.get("/", (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = c.get("effectiveSpace") || space;
	return c.html(renderShell({
		title: `${space} — Trips | rSpace`,
		moduleId: "rtrips",
		spaceSlug: space,
		modules: getModuleInfoList(),
		theme: "dark",
		body: `<folk-trips-planner space="${space}"></folk-trips-planner>`,
		scripts: `<script type="module" src="/modules/rtrips/folk-trips-planner.js?v=2"></script>`,
		styles: `<link rel="stylesheet" href="/modules/rtrips/trips.css">`,
	}));
});

// ── Seed template data ──

function seedTemplateTrips(space: string) {
	if (!_syncServer) return;
	// Skip if space already has trips
	const prefix = `${space}:trips:trips:`;
	const existing = _syncServer.listDocs().filter((id) => id.startsWith(prefix));
	if (existing.length > 0) return;

	const tripId = crypto.randomUUID();
	const docId = tripDocId(space, tripId);
	const now = Date.now();

	const dest1Id = crypto.randomUUID();
	const dest2Id = crypto.randomUUID();

	const doc = Automerge.change(Automerge.init<TripDoc>(), 'seed template trip', (d) => {
		d.meta = { module: 'trips', collection: 'trips', version: 1, spaceSlug: space, createdAt: now };
		d.trip = {
			id: tripId, title: 'Mediterranean Design Sprint', slug: 'mediterranean-design-sprint',
			description: 'A 5-day design sprint visiting Barcelona and Athens to prototype cosmolocal tools with local communities.',
			startDate: '2026-04-14', endDate: '2026-04-18', budgetTotal: 3200, budgetCurrency: 'EUR',
			status: 'planning', createdBy: 'did:demo:seed', createdAt: now, updatedAt: now,
		};
		d.destinations = {};
		d.destinations[dest1Id] = {
			id: dest1Id, tripId, name: 'Barcelona', country: 'Spain',
			lat: 41.3874, lng: 2.1686, arrivalDate: '2026-04-14', departureDate: '2026-04-16',
			notes: 'Meet with local maker space — prototype cosmolocal print workflow.', sortOrder: 0, createdAt: now,
		};
		d.destinations[dest2Id] = {
			id: dest2Id, tripId, name: 'Athens', country: 'Greece',
			lat: 37.9838, lng: 23.7275, arrivalDate: '2026-04-16', departureDate: '2026-04-18',
			notes: 'Commons Fest workshop — present rFlows river visualization.', sortOrder: 1, createdAt: now,
		};
		d.itinerary = {};
		const itin = [
			{ destId: dest1Id, title: 'Maker Space Visit', category: 'meeting', date: '2026-04-14', start: '10:00', end: '13:00', notes: 'Tour facilities, discuss print capabilities' },
			{ destId: dest1Id, title: 'Prototype Session', category: 'workshop', date: '2026-04-15', start: '09:00', end: '17:00', notes: 'Full-day sprint on cosmolocal order flow' },
			{ destId: dest2Id, title: 'Commons Fest Presentation', category: 'conference', date: '2026-04-17', start: '14:00', end: '16:00', notes: 'Present rFlows + rVote governance tools' },
		];
		for (let i = 0; i < itin.length; i++) {
			const iId = crypto.randomUUID();
			d.itinerary[iId] = {
				id: iId, tripId, destinationId: itin[i].destId, title: itin[i].title,
				category: itin[i].category, date: itin[i].date, startTime: itin[i].start,
				endTime: itin[i].end, notes: itin[i].notes, sortOrder: i, createdAt: now,
			};
		}
		d.bookings = {};
		const book = [
			{ type: 'flight', provider: 'Vueling', conf: 'VY-4821', cost: 180, start: '2026-04-14', end: '2026-04-14', notes: 'BCN arrival 09:30' },
			{ type: 'hotel', provider: 'Hotel Catalonia', conf: 'HC-9912', cost: 320, start: '2026-04-14', end: '2026-04-16', notes: '2 nights, old town' },
		];
		for (const b of book) {
			const bId = crypto.randomUUID();
			d.bookings[bId] = {
				id: bId, tripId, type: b.type, provider: b.provider, confirmationNumber: b.conf,
				cost: b.cost, currency: 'EUR', startDate: b.start, endDate: b.end,
				status: 'confirmed', notes: b.notes, createdAt: now,
			};
		}
		d.expenses = {};
		d.packingItems = {};
	});

	_syncServer.setDoc(docId, doc);
	console.log(`[Trips] Template seeded for "${space}": 1 trip, 2 destinations, 3 itinerary, 2 bookings`);
}

export function getRecentTripsForMI(space: string, limit = 5): { id: string; title: string; status: string; destinationCount: number; startDate: string; createdAt: number }[] {
	if (!_syncServer) return [];
	const items: { id: string; title: string; status: string; destinationCount: number; startDate: string; createdAt: number }[] = [];
	for (const docId of _syncServer.listDocs()) {
		if (!docId.startsWith(`${space}:trips:trips:`)) continue;
		const doc = _syncServer.getDoc<TripDoc>(docId);
		if (!doc?.trip) continue;
		const t = doc.trip;
		items.push({ id: t.id, title: t.title, status: t.status || "", destinationCount: Object.keys(doc.destinations || {}).length, startDate: t.startDate || "", createdAt: t.createdAt });
	}
	return items.sort((a, b) => b.createdAt - a.createdAt).slice(0, limit);
}

export const tripsModule: RSpaceModule = {
	id: "rtrips",
	name: "rTrips",
	icon: "✈️",
	description: "Collaborative trip planner with itinerary, bookings, and expense splitting",
	canvasShapes: ["folk-itinerary", "folk-destination", "folk-budget", "folk-packing-list", "folk-booking"],
	canvasToolIds: ["create_destination", "create_itinerary", "create_booking", "create_budget", "create_packing_list"],
	scoping: { defaultScope: 'global', userConfigurable: true },
	docSchemas: [{ pattern: '{space}:trips:trips:{tripId}', description: 'Trip with destinations and itinerary', init: tripSchema.init }],
	routes,
	landingPage: renderLanding,
	seedTemplate: seedTemplateTrips,
	async onInit(ctx) {
		_syncServer = ctx.syncServer;
	},
	standaloneDomain: "rtrips.online",
	feeds: [
		{
			id: "trip-expenses",
			name: "Trip Expenses",
			kind: "economic",
			description: "Expense tracking with split amounts per traveler",
			filterable: true,
		},
		{
			id: "itinerary",
			name: "Itinerary",
			kind: "data",
			description: "Destinations, activities, and bookings timeline",
			emits: ["folk-itinerary", "folk-destination", "folk-booking"],
		},
	],
	acceptsFeeds: ["economic", "data"],
	outputPaths: [
		{ path: "itineraries", name: "Itineraries", icon: "🗓️", description: "Trip itineraries with bookings and activities" },
	],
	subPageInfos: [
		{
			path: "routes",
			title: "Route Planner",
			icon: "🗺️",
			tagline: "rTrips Tool",
			description: "Plan multi-stop routes with distance and duration estimates. Optimize waypoints and export routes for navigation.",
			features: [
				{ icon: "📍", title: "Multi-Stop Planning", text: "Add waypoints, reorder stops, and calculate the optimal route." },
				{ icon: "⏱️", title: "Time & Distance", text: "See real-time estimates for travel time and distances between stops." },
				{ icon: "🗺️", title: "Map Visualization", text: "View your full route on an interactive map with turn-by-turn preview." },
			],
		},
	],
	onboardingActions: [
		{ label: "Plan a Trip", icon: "✈️", description: "Create your first trip itinerary", type: 'create', href: '/{space}/rtrips' },
	],
};
