/**
 * <folk-cart-shop> — group shopping carts, cosmolocal catalog, and orders.
 * Three-tab view: Carts (default) | Catalog | Orders
 */

import {
	catalogSchema, catalogDocId, type CatalogDoc,
	orderSchema, type OrderDoc,
	shoppingCartIndexSchema, shoppingCartIndexDocId, type ShoppingCartIndexDoc,
	shoppingCartSchema, shoppingCartDocId, type ShoppingCartDoc,
} from "../schemas";
import type { DocumentId } from "../../../shared/local-first/document";
import { TourEngine } from "../../../shared/tour-engine";
import { ViewHistory } from "../../../shared/view-history.js";

class FolkCartShop extends HTMLElement {
	private shadow: ShadowRoot;
	private space = "default";
	private catalog: any[] = [];
	private orders: any[] = [];
	private carts: any[] = [];
	private view: "carts" | "cart-detail" | "catalog" | "orders" = "carts";
	private selectedCartId: string | null = null;
	private selectedCart: any = null;
	private loading = true;
	private addingUrl = false;
	private contributingAmount = false;
	private extensionInstalled = false;
	private bannerDismissed = false;
	private _offlineUnsubs: (() => void)[] = [];
	private _history = new ViewHistory<"carts" | "cart-detail" | "catalog" | "orders">("carts");

	// Guided tour
	private _tour!: TourEngine;
	private static readonly TOUR_STEPS = [
		{ target: '[data-view="carts"]', title: "Carts", message: "View and manage group shopping carts. Each cart collects items from multiple contributors.", advanceOnClick: true },
		{ target: "[data-action='new-cart']", title: "Create a Cart", message: "Start a new group cart — add a name and invite contributors to add items.", advanceOnClick: true },
		{ target: '[data-view="catalog"]', title: "Catalog", message: "Browse the cosmolocal catalog of available products and add them to your carts.", advanceOnClick: true },
		{ target: '[data-view="orders"]', title: "Orders", message: "Track submitted orders and their status. Click to finish the tour!", advanceOnClick: true },
	];

	constructor() {
		super();
		this.shadow = this.attachShadow({ mode: "open" });
		this._tour = new TourEngine(
			this.shadow,
			FolkCartShop.TOUR_STEPS,
			"rcart_tour_done",
			() => this.shadow.host as HTMLElement,
		);
	}

	connectedCallback() {
		this.extensionInstalled = !!(window as any).__rspaceExtensionInstalled;
		this.bannerDismissed = localStorage.getItem('rcart-ext-banner-dismissed') === 'true';

		const attr = this.getAttribute("space");
		if (attr) {
			this.space = attr;
		} else {
			const parts = window.location.pathname.split("/").filter(Boolean);
			this.space = parts.length >= 1 ? parts[0] : "default";
		}

		if (this.space === "demo") {
			this.loadDemoData();
		} else {
			this.render();
			this.subscribeOffline();
			this.loadData();
		}
		// Auto-start tour on first visit
		if (!localStorage.getItem("rcart_tour_done")) {
			setTimeout(() => this._tour.start(), 1200);
		}
	}

	disconnectedCallback() {
		for (const unsub of this._offlineUnsubs) unsub();
		this._offlineUnsubs = [];
	}

	private async subscribeOffline() {
		const runtime = (window as any).__rspaceOfflineRuntime;
		if (!runtime?.isInitialized) return;

		try {
			// Subscribe to catalog
			const catDocId = catalogDocId(this.space) as DocumentId;
			const catDoc = await runtime.subscribe(catDocId, catalogSchema);
			if (catDoc?.items && Object.keys(catDoc.items).length > 0 && this.catalog.length === 0) {
				this.catalog = Object.values((catDoc as CatalogDoc).items).map(item => ({
					id: item.id, title: item.title, description: '',
					product_type: item.productType, status: item.status,
					tags: item.tags || [],
					created_at: new Date(item.createdAt).toISOString(),
				}));
				this.loading = false;
				this.render();
			}
			this._offlineUnsubs.push(runtime.onChange(catDocId, (doc: CatalogDoc) => {
				if (doc?.items) {
					this.catalog = Object.values(doc.items).map(item => ({
						id: item.id, title: item.title, description: '',
						product_type: item.productType, status: item.status,
						tags: item.tags || [],
						created_at: new Date(item.createdAt).toISOString(),
					}));
					this.render();
				}
			}));

			// Subscribe to shopping cart index
			const indexDocId = shoppingCartIndexDocId(this.space) as DocumentId;
			const indexDoc = await runtime.subscribe(indexDocId, shoppingCartIndexSchema);
			if (indexDoc?.carts && Object.keys(indexDoc.carts).length > 0) {
				this.carts = Object.entries((indexDoc as ShoppingCartIndexDoc).carts).map(([id, c]) => ({ id, ...c }));
				this.render();
			}
			this._offlineUnsubs.push(runtime.onChange(indexDocId, (doc: ShoppingCartIndexDoc) => {
				if (doc?.carts) {
					this.carts = Object.entries(doc.carts).map(([id, c]) => ({ id, ...c }));
					this.render();
				}
			}));

			// Subscribe to orders
			const orderDocs = await runtime.subscribeModule('cart', 'orders', orderSchema);
			if (orderDocs.size > 0 && this.orders.length === 0) {
				const fromDocs: any[] = [];
				for (const [, doc] of orderDocs) {
					const d = doc as OrderDoc;
					if (!d?.order) continue;
					fromDocs.push({
						id: d.order.id, status: d.order.status,
						quantity: d.order.quantity, total_price: d.order.totalPrice,
						currency: d.order.currency,
						created_at: new Date(d.order.createdAt).toISOString(),
					});
				}
				if (fromDocs.length > 0) {
					this.orders = fromDocs;
					this.render();
				}
			}
		} catch { /* runtime unavailable */ }
	}

	private loadDemoData() {
		const now = Date.now();

		// Demo shopping carts
		this.carts = [
			{
				id: "demo-cart-1",
				name: "Community Garden Project",
				status: "FUNDING",
				itemCount: 8,
				totalAmount: 247.83,
				fundedAmount: 165.00,
				currency: "USD",
				createdAt: new Date(now - 7 * 86400000).toISOString(),
				updatedAt: new Date(now - 1 * 86400000).toISOString(),
			},
			{
				id: "demo-cart-2",
				name: "Office Supplies",
				status: "OPEN",
				itemCount: 3,
				totalAmount: 45.97,
				fundedAmount: 0,
				currency: "USD",
				createdAt: new Date(now - 2 * 86400000).toISOString(),
				updatedAt: new Date(now - 2 * 86400000).toISOString(),
			},
			{
				id: "demo-cart-3",
				name: "Hackathon Merch Order",
				status: "ORDERED",
				itemCount: 5,
				totalAmount: 189.50,
				fundedAmount: 189.50,
				currency: "USD",
				createdAt: new Date(now - 14 * 86400000).toISOString(),
				updatedAt: new Date(now - 3 * 86400000).toISOString(),
			},
		];

		// Demo cart detail (Community Garden Project)
		this.selectedCart = {
			id: "demo-cart-1",
			name: "Community Garden Project",
			description: "Supplies for the community garden build day",
			status: "FUNDING",
			targetAmount: 300,
			fundedAmount: 165.00,
			currency: "USD",
			createdAt: new Date(now - 7 * 86400000).toISOString(),
			updatedAt: new Date(now - 1 * 86400000).toISOString(),
			items: [
				{ id: "i1", name: "Raised Garden Bed Kit (4x8 ft)", price: 79.99, currency: "USD", quantity: 1, sourceUrl: "https://amazon.com/dp/B08ABC1234", imageUrl: null, vendor: { name: "Amazon", domain: "amazon.com", platform: "amazon" }, addedAt: new Date(now - 6 * 86400000).toISOString() },
				{ id: "i2", name: "Organic Potting Soil (40 qt)", price: 12.99, currency: "USD", quantity: 3, sourceUrl: "https://amazon.com/dp/B08DEF5678", imageUrl: null, vendor: { name: "Amazon", domain: "amazon.com", platform: "amazon" }, addedAt: new Date(now - 6 * 86400000).toISOString() },
				{ id: "i3", name: "Heirloom Seed Variety Pack", price: 24.95, currency: "USD", quantity: 2, sourceUrl: "https://etsy.com/listing/123456", imageUrl: null, vendor: { name: "GardenSeedCo", domain: "etsy.com", platform: "etsy" }, addedAt: new Date(now - 5 * 86400000).toISOString() },
				{ id: "i4", name: "Garden Trowel Set", price: 15.99, currency: "USD", quantity: 1, sourceUrl: "https://amazon.com/dp/B08GHI9012", imageUrl: null, vendor: { name: "Amazon", domain: "amazon.com", platform: "amazon" }, addedAt: new Date(now - 5 * 86400000).toISOString() },
				{ id: "i5", name: "Drip Irrigation Kit", price: 29.99, currency: "USD", quantity: 1, sourceUrl: "https://amazon.com/dp/B08JKL3456", imageUrl: null, vendor: { name: "Amazon", domain: "amazon.com", platform: "amazon" }, addedAt: new Date(now - 4 * 86400000).toISOString() },
				{ id: "i6", name: "Plant Labels (100 pack)", price: 8.99, currency: "USD", quantity: 1, sourceUrl: "https://amazon.com/dp/B08MNO7890", imageUrl: null, vendor: { name: "Amazon", domain: "amazon.com", platform: "amazon" }, addedAt: new Date(now - 4 * 86400000).toISOString() },
				{ id: "i7", name: "Gardening Gloves (3 pair)", price: 12.99, currency: "USD", quantity: 1, sourceUrl: "https://amazon.com/dp/B08PQR1234", imageUrl: null, vendor: { name: "Amazon", domain: "amazon.com", platform: "amazon" }, addedAt: new Date(now - 3 * 86400000).toISOString() },
				{ id: "i8", name: "Compost Bin (80 gal)", price: 34.99, currency: "USD", quantity: 1, sourceUrl: "https://homedepot.com/p/12345", imageUrl: null, vendor: { name: "Home Depot", domain: "homedepot.com", platform: null }, addedAt: new Date(now - 2 * 86400000).toISOString() },
			],
			contributions: [
				{ id: "c1", username: "Alice", amount: 50, currency: "USD", paymentMethod: "MANUAL", status: "confirmed", createdAt: new Date(now - 5 * 86400000).toISOString() },
				{ id: "c2", username: "Bob", amount: 40, currency: "USD", paymentMethod: "MANUAL", status: "confirmed", createdAt: new Date(now - 4 * 86400000).toISOString() },
				{ id: "c3", username: "Carol", amount: 50, currency: "USD", paymentMethod: "MANUAL", status: "confirmed", createdAt: new Date(now - 3 * 86400000).toISOString() },
				{ id: "c4", username: "Dave", amount: 25, currency: "USD", paymentMethod: "MANUAL", status: "confirmed", createdAt: new Date(now - 1 * 86400000).toISOString() },
			],
			events: [],
		};

		// Existing catalog demo data
		this.catalog = [
			{ id: "demo-cat-1", title: "The Commons", description: "A pocket book exploring shared resources and collective stewardship.", price: 12, currency: "USD", tags: ["books"], product_type: "pocket book", status: "active", created_at: new Date(now - 30 * 86400000).toISOString() },
			{ id: "demo-cat-2", title: "Mycelium Networks", description: "Illustrated poster mapping underground fungal communication pathways.", price: 18, currency: "USD", tags: ["prints"], product_type: "poster", status: "active", created_at: new Date(now - 25 * 86400000).toISOString() },
			{ id: "demo-cat-3", title: "#DefectFi", description: "Organic cotton tee shirt with the #DefectFi campaign logo.", price: 25, currency: "USD", tags: ["apparel"], product_type: "tee shirt", status: "active", created_at: new Date(now - 20 * 86400000).toISOString() },
			{ id: "demo-cat-4", title: "Cosmolocal Sticker Sheet", description: "Die-cut sticker sheet with cosmolocal design motifs.", price: 5, currency: "USD", tags: ["stickers"], product_type: "sticker sheet", status: "active", created_at: new Date(now - 15 * 86400000).toISOString() },
			{ id: "demo-cat-5", title: "Doughnut Economics", description: "A zine breaking down Kate Raworth's doughnut economics framework.", price: 8, currency: "USD", tags: ["books"], product_type: "zine", status: "active", created_at: new Date(now - 10 * 86400000).toISOString() },
			{ id: "demo-cat-6", title: "rSpace Logo", description: "Embroidered patch featuring the rSpace logo on twill backing.", price: 6, currency: "USD", tags: ["accessories"], product_type: "embroidered patch", status: "active", created_at: new Date(now - 5 * 86400000).toISOString() },
			{ id: "demo-cat-7", title: "Cosmolocal Network Tee", description: "Bella+Canvas 3001 tee with the Cosmolocal Network radial design.", price: 25, currency: "USD", tags: ["apparel", "cosmolocal"], product_type: "tee", required_capabilities: ["dtg-print"], status: "active", created_at: new Date(now - 3 * 86400000).toISOString() },
			{ id: "demo-cat-8", title: "Cosmolocal Sticker Sheet", description: "Kiss-cut vinyl sticker sheet with cosmolocal network motifs.", price: 5, currency: "USD", tags: ["stickers", "cosmolocal"], product_type: "sticker-sheet", required_capabilities: ["vinyl-cut"], status: "active", created_at: new Date(now - 1 * 86400000).toISOString() },
		];

		this.orders = [
			{ id: "demo-ord-1001", total_price: "30.00", currency: "USD", status: "paid", created_at: new Date(now - 2 * 86400000).toISOString(), artifact_title: "Order #1001", quantity: 2 },
			{ id: "demo-ord-1002", total_price: "25.00", currency: "USD", status: "pending", created_at: new Date(now - 1 * 86400000).toISOString(), artifact_title: "Order #1002", quantity: 1 },
			{ id: "demo-ord-1003", total_price: "23.00", currency: "USD", status: "shipped", created_at: new Date(now - 5 * 86400000).toISOString(), artifact_title: "Order #1003", quantity: 3 },
		];

		this.loading = false;
		this.render();
	}

	private getApiBase(): string {
		const path = window.location.pathname;
		const match = path.match(/^(\/[^/]+)?\/rcart/);
		return match ? match[0] : "/rcart";
	}

	private async loadData() {
		this.loading = true;
		this.render();
		try {
			const [catRes, ordRes, cartRes] = await Promise.all([
				fetch(`${this.getApiBase()}/api/catalog?limit=50`),
				fetch(`${this.getApiBase()}/api/orders?limit=20`),
				fetch(`${this.getApiBase()}/api/shopping-carts`),
			]);
			const catData = await catRes.json();
			const ordData = await ordRes.json();
			const cartData = await cartRes.json();
			this.catalog = catData.entries || [];
			this.orders = ordData.orders || [];
			this.carts = cartData.carts || [];
		} catch (e) {
			console.error("Failed to load cart data:", e);
		}
		this.loading = false;
		this.render();
	}

	private async loadCartDetail(cartId: string) {
		if (this.space === "demo") {
			this.selectedCartId = cartId;
			this.view = "cart-detail";
			this.render();
			return;
		}
		try {
			const res = await fetch(`${this.getApiBase()}/api/shopping-carts/${cartId}`);
			this.selectedCart = await res.json();
			this.selectedCartId = cartId;
			this.view = "cart-detail";
		} catch (e) {
			console.error("Failed to load cart:", e);
		}
		this.render();
	}

	private async createCart(name: string, targetAmount: number) {
		if (this.space === "demo") return;
		try {
			await fetch(`${this.getApiBase()}/api/shopping-carts`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ name, targetAmount }),
			});
			await this.loadData();
		} catch (e) {
			console.error("Failed to create cart:", e);
		}
	}

	private async addItemByUrl(cartId: string, url: string) {
		this.addingUrl = true;
		this.render();
		try {
			await fetch(`${this.getApiBase()}/api/shopping-carts/${cartId}/items`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ url }),
			});
			await this.loadCartDetail(cartId);
		} catch (e) {
			console.error("Failed to add item:", e);
		}
		this.addingUrl = false;
	}

	private async removeItem(cartId: string, itemId: string) {
		try {
			await fetch(`${this.getApiBase()}/api/shopping-carts/${cartId}/items/${itemId}`, { method: 'DELETE' });
			await this.loadCartDetail(cartId);
		} catch (e) {
			console.error("Failed to remove item:", e);
		}
	}

	private async contribute(cartId: string, amount: number, username: string) {
		try {
			await fetch(`${this.getApiBase()}/api/shopping-carts/${cartId}/contribute`, {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ amount, username }),
			});
			await this.loadCartDetail(cartId);
		} catch (e) {
			console.error("Failed to contribute:", e);
		}
	}

	// ── Main render ──

	private render() {
		const styles = this.getStyles();
		let content = '';

		if (this.loading) {
			content = `<div class="loading">Loading...</div>`;
		} else if (this.view === "carts") {
			content = this.renderCarts();
		} else if (this.view === "cart-detail") {
			content = this.renderCartDetail();
		} else if (this.view === "catalog") {
			content = this.renderCatalog();
		} else {
			content = this.renderOrders();
		}

		this.shadow.innerHTML = `
		<style>${styles}</style>
		<div class="rapp-nav">
			<span class="rapp-nav__title">Shop</span>
			<div class="tabs">
				<button class="tab ${this.view === 'carts' || this.view === 'cart-detail' ? 'active' : ''}" data-view="carts">🛒 Carts (${this.carts.length})</button>
				<button class="tab ${this.view === 'catalog' ? 'active' : ''}" data-view="catalog">📦 Catalog (${this.catalog.length})</button>
				<button class="tab ${this.view === 'orders' ? 'active' : ''}" data-view="orders">📋 Orders (${this.orders.length})</button>
			</div>
			<button class="tab" id="btn-tour" style="margin-left:auto;font-size:0.8rem">Tour</button>
		</div>
		${content}
		`;

		this.bindEvents();
		this._tour.renderOverlay();
	}

	startTour() { this._tour.start(); }

	private goBack() {
		const prev = this._history.back();
		if (!prev) return;
		this.view = prev.view;
		if (prev.view !== "cart-detail") this.selectedCartId = null;
		this.render();
	}

	private bindEvents() {
		// Tab switching
		this.shadow.querySelectorAll(".tab").forEach((el) => {
			el.addEventListener("click", () => {
				const v = (el as HTMLElement).dataset.view as any;
				if (v && v !== this.view) {
					this._history.push(this.view);
					this._history.push(v);
				}
				this.view = v;
				if (v === 'carts') this.selectedCartId = null;
				this.render();
			});
		});

		// Tour button
		this.shadow.getElementById("btn-tour")?.addEventListener("click", () => this.startTour());

		// Extension banner dismiss
		this.shadow.querySelector("[data-action='dismiss-banner']")?.addEventListener("click", () => {
			this.bannerDismissed = true;
			localStorage.setItem('rcart-ext-banner-dismissed', 'true');
			this.render();
		});

		// Cart card clicks
		this.shadow.querySelectorAll("[data-cart-id]").forEach((el) => {
			el.addEventListener("click", () => {
				this._history.push(this.view);
				this._history.push("cart-detail", { cartId: (el as HTMLElement).dataset.cartId });
				this.loadCartDetail((el as HTMLElement).dataset.cartId!);
			});
		});

		// Back button
		this.shadow.querySelector("[data-action='back']")?.addEventListener("click", () => {
			this.goBack();
		});

		// New cart form
		const newCartBtn = this.shadow.querySelector("[data-action='new-cart']");
		newCartBtn?.addEventListener("click", () => {
			const form = this.shadow.querySelector(".new-cart-form") as HTMLElement;
			if (form) form.style.display = form.style.display === 'none' ? 'flex' : 'none';
		});
		this.shadow.querySelector("[data-action='create-cart']")?.addEventListener("click", () => {
			const nameInput = this.shadow.querySelector("[data-field='cart-name']") as HTMLInputElement;
			const amountInput = this.shadow.querySelector("[data-field='cart-target']") as HTMLInputElement;
			if (nameInput?.value) this.createCart(nameInput.value, parseFloat(amountInput?.value) || 0);
		});

		// Add item by URL
		this.shadow.querySelector("[data-action='add-url']")?.addEventListener("click", () => {
			const input = this.shadow.querySelector("[data-field='item-url']") as HTMLInputElement;
			if (input?.value && this.selectedCartId) {
				this.addItemByUrl(this.selectedCartId, input.value);
			}
		});

		// Remove item
		this.shadow.querySelectorAll("[data-remove-item]").forEach((el) => {
			el.addEventListener("click", (e) => {
				e.stopPropagation();
				if (this.selectedCartId) this.removeItem(this.selectedCartId, (el as HTMLElement).dataset.removeItem!);
			});
		});

		// Contribute toggle
		this.shadow.querySelector("[data-action='show-contribute']")?.addEventListener("click", () => {
			const form = this.shadow.querySelector(".contribute-form") as HTMLElement;
			if (form) form.style.display = form.style.display === 'none' ? 'flex' : 'none';
		});
		this.shadow.querySelector("[data-action='submit-contribute']")?.addEventListener("click", () => {
			const amtInput = this.shadow.querySelector("[data-field='contrib-amount']") as HTMLInputElement;
			const nameInput = this.shadow.querySelector("[data-field='contrib-name']") as HTMLInputElement;
			if (amtInput?.value && this.selectedCartId) {
				this.contribute(this.selectedCartId, parseFloat(amtInput.value), nameInput?.value || 'Anonymous');
			}
		});
	}

	// ── Extension install banner ──

	private renderExtensionBanner(): string {
		if (this.extensionInstalled || this.bannerDismissed) return '';
		return `
			<div class="ext-banner">
				<button class="ext-banner-dismiss" data-action="dismiss-banner" title="Dismiss">&times;</button>
				<div class="ext-banner-title">Add products from any store</div>
				<div class="ext-banner-text">Install the rSpace browser extension to add items from Amazon, Etsy, Shopify, and more directly to your carts.</div>
				<a class="ext-banner-cta" href="https://rspace.online/extension" target="_blank" rel="noopener">Get Extension</a>
			</div>`;
	}

	// ── Cart list view ──

	private renderCarts(): string {
		const newCartForm = `
			<div class="new-cart-form" style="display:none">
				<input data-field="cart-name" type="text" placeholder="Cart name" class="input" />
				<input data-field="cart-target" type="number" placeholder="Target amount (optional)" class="input input-sm" />
				<button data-action="create-cart" class="btn btn-primary btn-sm">Create</button>
			</div>`;

		if (this.carts.length === 0) {
			return `
				${this.renderExtensionBanner()}
				<div class="empty">
					<p>No shopping carts yet. Create one to start group shopping.</p>
					<button data-action="new-cart" class="btn btn-primary">+ New Cart</button>
					${newCartForm}
				</div>`;
		}

		return `
			${this.renderExtensionBanner()}
			<div style="margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem;">
				<button data-action="new-cart" class="btn btn-primary btn-sm">+ New Cart</button>
				${newCartForm}
			</div>
			<div class="grid">
				${this.carts.map((cart) => {
					const pct = cart.totalAmount > 0 ? Math.min(100, Math.round((cart.fundedAmount / cart.totalAmount) * 100)) : 0;
					return `
					<div class="card card-clickable" data-cart-id="${cart.id}">
						<div style="display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:0.5rem">
							<h3 class="card-title" style="margin:0">${this.esc(cart.name)}</h3>
							<span class="status status-${cart.status.toLowerCase()}">${cart.status}</span>
						</div>
						<div class="card-meta">${cart.itemCount} item${cart.itemCount !== 1 ? 's' : ''} &bull; $${cart.totalAmount.toFixed(2)}</div>
						<div class="progress-bar">
							<div class="progress-fill" style="width: ${pct}%"></div>
						</div>
						<div class="card-meta" style="margin-top:0.25rem">$${cart.fundedAmount.toFixed(2)} / $${cart.totalAmount.toFixed(2)} funded (${pct}%)</div>
					</div>`;
				}).join("")}
			</div>`;
	}

	// ── Cart detail view ──

	private renderCartDetail(): string {
		const cart = this.selectedCart;
		if (!cart) return `<div class="loading">Loading cart...</div>`;

		const items = cart.items || [];
		const contributions = cart.contributions || [];
		const totalItemsCost = items.reduce((s: number, i: any) => s + (i.price || 0) * (i.quantity || 1), 0);
		const targetDisplay = cart.targetAmount > 0 ? cart.targetAmount : totalItemsCost;
		const pct = targetDisplay > 0 ? Math.min(100, Math.round((cart.fundedAmount / targetDisplay) * 100)) : 0;
		const remaining = Math.max(0, targetDisplay - cart.fundedAmount);

		// Group items by vendor domain
		const vendorGroups: Record<string, any[]> = {};
		for (const item of items) {
			const key = item.vendor?.domain || 'other';
			if (!vendorGroups[key]) vendorGroups[key] = [];
			vendorGroups[key].push(item);
		}

		const itemsHtml = Object.entries(vendorGroups).map(([domain, vendorItems]) => `
			<div class="vendor-group">
				<div class="vendor-header">${this.esc(domain)}</div>
				${vendorItems.map((item: any) => `
					<div class="item-row">
						${item.imageUrl ? `<img class="item-thumb" src="${this.esc(item.imageUrl)}" alt="" />` : `<div class="item-thumb item-thumb-placeholder">📦</div>`}
						<div class="item-info">
							<a class="item-name" href="${this.esc(item.sourceUrl)}" target="_blank" rel="noopener">${this.esc(item.name)}</a>
							<div class="item-meta">Qty: ${item.quantity}${item.price != null ? ` &bull; $${(item.price * item.quantity).toFixed(2)}` : ''}</div>
						</div>
						<button class="btn-icon" data-remove-item="${item.id}" title="Remove">✕</button>
					</div>
				`).join("")}
			</div>
		`).join("");

		const contribHtml = contributions.length > 0 ? contributions.map((c: any) => `
			<div class="contrib-row">
				<span class="contrib-name">${this.esc(c.username)}</span>
				<span class="contrib-amount">$${c.amount.toFixed(2)}</span>
				<span class="status status-${c.status}">${c.status}</span>
			</div>
		`).join("") : `<div class="card-meta">No contributions yet.</div>`;

		return `
			${this._history.canGoBack ? '<div style="margin-bottom:1rem"><button data-action="back" class="btn btn-sm">\u2190 Back</button></div>' : ''}

			<div class="detail-layout">
				<div class="detail-left">
					<h2 class="section-title">${this.esc(cart.name)}</h2>
					${cart.description ? `<p class="card-meta">${this.esc(cart.description)}</p>` : ''}

					<div class="url-input-row">
						<input data-field="item-url" type="url" placeholder="Paste product URL from any store..." class="input" style="flex:1" />
						<button data-action="add-url" class="btn btn-primary btn-sm" ${this.addingUrl ? 'disabled' : ''}>${this.addingUrl ? 'Adding...' : 'Add'}</button>
					</div>

					${items.length === 0
						? `<div class="empty">No items yet. Paste a URL above to add products.</div>`
						: itemsHtml}
				</div>

				<div class="detail-right">
					<div class="card">
						<h3 class="card-title">Summary</h3>
						<div class="summary-row"><span>Items cost</span><span>$${totalItemsCost.toFixed(2)}</span></div>
						<div class="summary-row"><span>Funded</span><span class="text-green">$${cart.fundedAmount.toFixed(2)}</span></div>
						<div class="summary-row"><span>Remaining</span><span>$${remaining.toFixed(2)}</span></div>
						<div class="progress-bar" style="margin:0.75rem 0">
							<div class="progress-fill" style="width: ${pct}%"></div>
						</div>
						<div class="card-meta" style="text-align:center">${pct}% funded</div>

						<button data-action="show-contribute" class="btn btn-primary" style="width:100%; margin-top:1rem">Contribute</button>
						<div class="contribute-form" style="display:none">
							<input data-field="contrib-name" type="text" placeholder="Your name" class="input" />
							<input data-field="contrib-amount" type="number" placeholder="Amount ($)" class="input" step="0.01" min="0.01" />
							<button data-action="submit-contribute" class="btn btn-primary btn-sm">Confirm</button>
						</div>
					</div>

					<div class="card" style="margin-top:1rem">
						<h3 class="card-title">Contributions</h3>
						${contribHtml}
					</div>
				</div>
			</div>`;
	}

	// ── Catalog view ──

	private renderCatalog(): string {
		if (this.catalog.length === 0) {
			return `<div class="empty">No items in the catalog yet. Ingest artifacts from rPubs or Swag Designer to list them here.</div>`;
		}

		return `<div class="grid">
			${this.catalog.map((entry) => `
			<div class="card" data-collab-id="product:${entry.id || entry.title}">
				<h3 class="card-title">${this.esc(entry.title || "Untitled")}</h3>
				<div class="card-meta">
					${entry.product_type ? `<span class="tag tag-type">${this.esc(entry.product_type)}</span>` : ""}
					${(entry.required_capabilities || []).map((cap: string) => `<span class="tag tag-cap">${this.esc(cap)}</span>`).join("")}
					${(entry.tags || []).map((t: string) => `<span class="tag tag-cap">${this.esc(t)}</span>`).join("")}
				</div>
				${entry.description ? `<div class="card-meta">${this.esc(entry.description)}</div>` : ""}
				${entry.dimensions ? `<div class="dims">${entry.dimensions.width_mm}x${entry.dimensions.height_mm}mm</div>` : ""}
				${entry.price != null ? `<div class="price">$${parseFloat(entry.price).toFixed(2)} ${entry.currency || ""}</div>` : ""}
				<div style="margin-top:0.5rem"><span class="status status-${entry.status}">${entry.status}</span></div>
			</div>
			`).join("")}
		</div>`;
	}

	// ── Orders view ──

	private renderOrders(): string {
		if (this.orders.length === 0) {
			return `<div class="empty">No orders yet.</div>`;
		}

		return `<div class="grid">
			${this.orders.map((order) => `
			<div class="card" data-collab-id="order:${order.id}">
				<div class="order-card">
					<div class="order-info">
						<h3 class="card-title">${this.esc(order.artifact_title || "Order")}</h3>
						<div class="card-meta">
							${order.provider_name ? `Provider: ${this.esc(order.provider_name)}` : ""}
							${order.quantity > 1 ? ` &bull; Qty: ${order.quantity}` : ""}
						</div>
						<span class="status status-${order.status}">${order.status}</span>
					</div>
					<div class="order-price">$${parseFloat(order.total_price || 0).toFixed(2)}</div>
				</div>
			</div>
			`).join("")}
		</div>`;
	}

	// ── Styles ──

	private getStyles(): string {
		return `
			:host { display: block; padding: 1.5rem; }
			* { box-sizing: border-box; }
			.rapp-nav { display: flex; gap: 8px; align-items: center; margin-bottom: 1rem; min-height: 36px; flex-wrap: wrap; }
			.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: var(--rs-text-primary); }
			.tabs { display: flex; gap: 0.5rem; }
			.tab { padding: 0.5rem 1rem; border-radius: 8px; border: 1px solid var(--rs-border); background: var(--rs-bg-surface); color: var(--rs-text-secondary); cursor: pointer; font-size: 0.875rem; }
			.tab:hover { border-color: var(--rs-border-strong); color: var(--rs-text-primary); }
			.tab.active { background: var(--rs-primary-hover); border-color: var(--rs-primary); color: #fff; }

			.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1rem; }
			.card { background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 12px; padding: 1.25rem; }
			.card:hover { border-color: var(--rs-border-strong); }
			.card-clickable { cursor: pointer; transition: border-color 0.15s, transform 0.1s; }
			.card-clickable:hover { border-color: var(--rs-primary); transform: translateY(-1px); }
			.card-title { color: var(--rs-text-primary); font-weight: 600; font-size: 1rem; margin: 0 0 0.5rem; }
			.card-meta { color: var(--rs-text-secondary); font-size: 0.8125rem; margin-bottom: 0.5rem; }
			.section-title { color: var(--rs-text-primary); font-weight: 700; font-size: 1.25rem; margin: 0 0 0.5rem; }

			.tag { display: inline-block; padding: 0.125rem 0.5rem; border-radius: 4px; font-size: 0.6875rem; margin-right: 0.25rem; }
			.tag-type { background: rgba(99,102,241,0.1); color: var(--rs-primary-hover); }
			.tag-cap { background: rgba(34,197,94,0.1); color: #4ade80; }
			.dims { color: var(--rs-text-muted); font-size: 0.75rem; margin-top: 0.5rem; }

			.status { padding: 0.125rem 0.5rem; border-radius: 999px; font-size: 0.6875rem; font-weight: 500; white-space: nowrap; }
			.status-pending, .status-open { background: rgba(251,191,36,0.15); color: #fbbf24; }
			.status-paid, .status-active, .status-confirmed, .status-funded { background: rgba(34,197,94,0.15); color: #4ade80; }
			.status-funding { background: rgba(99,102,241,0.15); color: #a5b4fc; }
			.status-completed, .status-ordered, .status-checking_out { background: rgba(99,102,241,0.15); color: #a5b4fc; }
			.status-cancelled, .status-closed { background: rgba(239,68,68,0.15); color: #f87171; }
			.status-shipped { background: rgba(56,189,248,0.15); color: #38bdf8; }

			.price { color: var(--rs-text-primary); font-weight: 600; font-size: 1rem; margin-top: 0.5rem; }
			.text-green { color: #4ade80; }

			.progress-bar { background: var(--rs-bg-surface-raised); border-radius: 999px; height: 8px; overflow: hidden; }
			.progress-fill { background: linear-gradient(90deg, var(--rs-primary), #8b5cf6); height: 100%; border-radius: 999px; transition: width 0.3s; }

			.btn { padding: 0.5rem 1rem; border-radius: 8px; border: 1px solid var(--rs-border); background: var(--rs-bg-surface); color: var(--rs-text-primary); cursor: pointer; font-size: 0.875rem; }
			.btn:hover { border-color: var(--rs-border-strong); }
			.btn-primary { background: var(--rs-primary-hover); border-color: var(--rs-primary); color: #fff; }
			.btn-primary:hover { background: #4338ca; }
			.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
			.btn-sm { padding: 0.375rem 0.75rem; font-size: 0.8125rem; }
			.btn-icon { background: none; border: none; color: var(--rs-text-muted); cursor: pointer; padding: 4px; font-size: 0.875rem; border-radius: 4px; }
			.btn-icon:hover { color: #f87171; background: rgba(239,68,68,0.1); }

			.input { padding: 0.5rem 0.75rem; border-radius: 8px; border: 1px solid var(--rs-input-border); background: var(--rs-input-bg); color: var(--rs-input-text); font-size: 0.875rem; }
			.input:focus { outline: none; border-color: var(--rs-primary); }
			.input-sm { max-width: 160px; }

			.new-cart-form, .contribute-form { display: flex; gap: 0.5rem; align-items: center; margin-top: 0.5rem; flex-wrap: wrap; }

			.url-input-row { display: flex; gap: 0.5rem; margin-bottom: 1rem; }

			.detail-layout { display: grid; grid-template-columns: 1fr 320px; gap: 1.5rem; }
			@media (max-width: 768px) { .detail-layout { grid-template-columns: 1fr; } }

			.vendor-group { margin-bottom: 1rem; }
			.vendor-header { color: var(--rs-text-secondary); font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; padding: 0.5rem 0; border-bottom: 1px solid var(--rs-border-subtle); margin-bottom: 0.5rem; }
			.item-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem 0; border-bottom: 1px solid var(--rs-border-subtle); }
			.item-thumb { width: 40px; height: 40px; border-radius: 6px; object-fit: cover; background: var(--rs-bg-surface-raised); flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-size: 1.25rem; }
			.item-thumb-placeholder { width: 40px; height: 40px; border-radius: 6px; background: var(--rs-bg-surface-raised); display: flex; align-items: center; justify-content: center; font-size: 1.25rem; }
			.item-info { flex: 1; min-width: 0; }
			.item-name { color: var(--rs-text-primary); font-size: 0.875rem; text-decoration: none; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
			.item-name:hover { color: var(--rs-primary-hover); }
			.item-meta { color: var(--rs-text-muted); font-size: 0.75rem; }

			.summary-row { display: flex; justify-content: space-between; padding: 0.375rem 0; color: var(--rs-text-primary); font-size: 0.875rem; }

			.contrib-row { display: flex; align-items: center; gap: 0.5rem; padding: 0.375rem 0; font-size: 0.8125rem; }
			.contrib-name { color: var(--rs-text-primary); flex: 1; }
			.contrib-amount { color: #4ade80; font-weight: 600; }

			.order-card { display: flex; justify-content: space-between; align-items: center; }
			.order-info { flex: 1; }
			.order-price { color: var(--rs-text-primary); font-weight: 600; font-size: 1.125rem; }

			.ext-banner { position: relative; background: rgba(99,102,241,0.08); border: 1px solid rgba(99,102,241,0.25); border-radius: 12px; padding: 1rem 2.5rem 1rem 1.25rem; margin-bottom: 1rem; }
			.ext-banner-dismiss { position: absolute; top: 0.5rem; right: 0.75rem; background: none; border: none; color: var(--rs-text-secondary); font-size: 1.25rem; cursor: pointer; padding: 0 4px; line-height: 1; }
			.ext-banner-dismiss:hover { color: var(--rs-text-primary); }
			.ext-banner-title { color: #a5b4fc; font-weight: 600; font-size: 0.9375rem; margin-bottom: 0.25rem; }
			.ext-banner-text { color: var(--rs-text-secondary); font-size: 0.8125rem; margin-bottom: 0.75rem; line-height: 1.4; }
			.ext-banner-cta { display: inline-block; padding: 0.375rem 1rem; border-radius: 6px; background: var(--rs-primary-hover); color: #fff; font-size: 0.8125rem; font-weight: 600; text-decoration: none; }
			.ext-banner-cta:hover { background: #4338ca; }

			.empty { text-align: center; padding: 3rem; color: var(--rs-text-muted); font-size: 0.875rem; }
			.loading { text-align: center; padding: 3rem; color: var(--rs-text-secondary); }

			@media (max-width: 480px) {
				.grid { grid-template-columns: 1fr; }
				.url-input-row { flex-direction: column; }
			}
		`;
	}

	private esc(s: string): string {
		const d = document.createElement("div");
		d.textContent = s;
		return d.innerHTML;
	}
}

customElements.define("folk-cart-shop", FolkCartShop);
