/**
 * Swag module — design tool for print-ready swag (stickers, posters, tees).
 *
 * Ported from /opt/apps/swag-designer/ (Next.js → Hono).
 * Uses Sharp for image processing. No database needed (filesystem storage).
 */

import { Hono } from "hono";
import { randomUUID } from "node:crypto";
import { mkdir, writeFile, readFile, readdir, stat, rm } from "node:fs/promises";
import { join } from "node:path";
import sharp from "sharp";
import { getProduct, PRODUCTS, getPodProducts, type StorefrontProduct } from "./products";
import { processImage } from "./process-image";
import { ditherDesign, generateColorSeparations, ALGORITHMS, type DitherAlgorithm } from "./dither";
import { generateMockup } from "./mockup";
import { submitFulfillment, parsePrintfulWebhook, parseProdigiWebhook, getFulfillmentStatus, getTrackingInfo, type FulfillmentRequest } from "./fulfillment";
import { renderShell } from "../../server/shell";
import { getModuleInfoList } from "../../shared/module";
import type { RSpaceModule } from "../../shared/module";
import { renderLanding } from "./landing";
import * as Automerge from "@automerge/automerge";
import { verifyToken, extractToken } from "../../server/auth";
import type { SyncServer } from '../../server/local-first/sync-server';
import { swagSchema, swagDocId } from './schemas';
import type { SwagDoc } from './schemas';

let _syncServer: SyncServer | null = null;

function ensureSwagDoc(space: string): SwagDoc {
    const docId = swagDocId(space);
    let doc = _syncServer!.getDoc<SwagDoc>(docId);
    if (!doc) {
        doc = Automerge.change(Automerge.init<SwagDoc>(), 'init swag', (d) => {
            const init = swagSchema.init();
            Object.assign(d, init);
            d.meta.spaceSlug = space;
        });
        _syncServer!.setDoc(docId, doc);
    }
    return doc;
}

const routes = new Hono();

const ARTIFACTS_DIR = process.env.SWAG_ARTIFACTS_DIR || "/tmp/swag-artifacts";

// ── GET /api/products — List available product templates ──
routes.get("/api/products", (c) => {
	const products = Object.values(PRODUCTS).map((p) => ({
		id: p.id,
		name: p.name,
		description: p.description,
		printArea: p.printArea,
		dpi: p.dpi,
		bleedMm: p.bleedMm,
		widthPx: p.widthPx,
		heightPx: p.heightPx,
		productType: p.productType,
		substrates: p.substrates,
		requiredCapabilities: p.requiredCapabilities,
		finish: p.finish,
		...(p.printful ? { printful: p.printful } : {}),
	}));
	return c.json({ products });
});

// ── POST /api/generate — Preview without persistence ──
routes.post("/api/generate", async (c) => {
	try {
		const formData = await c.req.formData();
		const imageFile = formData.get("image") as File | null;
		const productId = formData.get("product") as string | null;

		if (!imageFile) return c.json({ error: "image file is required" }, 400);
		if (!productId) return c.json({ error: "product is required (sticker, poster, tee)" }, 400);

		const product = getProduct(productId);
		if (!product) return c.json({ error: `Unknown product: ${productId}` }, 400);

		const inputBuffer = Buffer.from(await imageFile.arrayBuffer());
		const result = await processImage(inputBuffer, product);

		return new Response(new Uint8Array(result.buffer), {
			status: 200,
			headers: {
				"Content-Type": "image/png",
				"Content-Disposition": `attachment; filename="${productId}-print-ready.png"`,
				"Content-Length": String(result.sizeBytes),
				"X-Width-Px": String(result.widthPx),
				"X-Height-Px": String(result.heightPx),
				"X-DPI": String(result.dpi),
			},
		});
	} catch (error) {
		console.error("Generate error:", error);
		return c.json({ error: error instanceof Error ? error.message : "Processing failed" }, 500);
	}
});

// ── POST /api/artifact — Create artifact with persistence ──
routes.post("/api/artifact", async (c) => {
	try {
		const formData = await c.req.formData();
		const imageFile = formData.get("image") as File | null;
		const productId = formData.get("product") as string | null;

		const title = (formData.get("title") as string) || "Untitled Design";
		const description = formData.get("description") as string | null;
		const creatorId = formData.get("creator_id") as string | null;
		const creatorName = formData.get("creator_name") as string | null;
		const creatorWallet = formData.get("creator_wallet") as string | null;
		const sourceSpace = formData.get("source_space") as string | null;
		const license = formData.get("license") as string | null;
		const tagsRaw = formData.get("tags") as string | null;
		const tags = tagsRaw ? tagsRaw.split(",").map((t) => t.trim()) : [];
		const creatorSharePct = parseFloat((formData.get("creator_share_pct") as string) || "25");
		const communitySharePct = parseFloat((formData.get("community_share_pct") as string) || "10");

		if (!imageFile) return c.json({ error: "image file is required" }, 400);
		if (!productId || !getProduct(productId)) {
			return c.json({ error: `product is required. Available: ${Object.keys(PRODUCTS).join(", ")}` }, 400);
		}

		const product = getProduct(productId)!;
		const inputBuffer = Buffer.from(await imageFile.arrayBuffer());
		const result = await processImage(inputBuffer, product);

		// Store artifact
		const artifactId = randomUUID();
		const artifactDir = join(ARTIFACTS_DIR, artifactId);
		await mkdir(artifactDir, { recursive: true });

		const filename = `${productId}-print-ready.png`;
		await writeFile(join(artifactDir, filename), result.buffer);
		await writeFile(join(artifactDir, `original-${imageFile.name}`), inputBuffer);

		// Build artifact envelope
		const proto = c.req.header("x-forwarded-proto") || "https";
		const host = c.req.header("host") || "rswag.online";
		const baseUrl = `${proto}://${host}`;
		const renderTargetKey = `${product.id}-${result.format}`;

		// Build the URL — in unified mode, we're mounted under /:space/swag
		const path = c.req.path.replace("/api/artifact", "");
		const mountBase = c.req.url.includes("/swag/") ? c.req.url.split("/swag/")[0] + "/swag" : "";
		const fileUrl = mountBase ? `${baseUrl}${mountBase}/api/artifact/${artifactId}/file` : `${baseUrl}/api/artifact/${artifactId}/file`;

		const artifact = {
			id: artifactId,
			schema_version: "1.0",
			type: "print-ready",
			origin: "rswag.online",
			source_space: sourceSpace || null,
			creator: {
				id: creatorId || "anonymous",
				...(creatorName ? { name: creatorName } : {}),
				...(creatorWallet ? { wallet: creatorWallet } : {}),
			},
			created_at: new Date().toISOString(),
			...(license ? { license } : {}),
			payload: {
				title,
				...(description ? { description } : {
					description: `${product.name} design — ${result.widthPx}x${result.heightPx}px at ${result.dpi}dpi`,
				}),
				...(tags.length > 0 ? { tags } : {}),
			},
			spec: {
				product_type: product.productType,
				dimensions: {
					width_mm: product.printArea.widthMm,
					height_mm: product.printArea.heightMm,
					bleed_mm: product.bleedMm,
				},
				color_space: "CMYK",
				dpi: product.dpi,
				binding: "none",
				finish: product.finish,
				substrates: product.substrates,
				required_capabilities: product.requiredCapabilities,
			},
			render_targets: {
				[renderTargetKey]: {
					url: fileUrl,
					format: result.format,
					dpi: result.dpi,
					file_size_bytes: result.sizeBytes,
					notes: `${product.name} print-ready file. ${result.widthPx}x${result.heightPx}px.`,
				},
			},
			pricing: {
				creator_share_pct: creatorSharePct,
				community_share_pct: communitySharePct,
			},
			next_actions: [
				{ tool: "rcart.online", action: "list-for-sale", label: "Sell in community shop", endpoint: "/api/catalog/ingest", method: "POST" },
				{ tool: "rswag.online", action: "edit-design", label: "Edit design", endpoint: "/editor", method: "GET" },
				{ tool: "rfiles.online", action: "archive", label: "Save to files", endpoint: "/api/v1/files/import", method: "POST" },
			],
		};

		await writeFile(join(artifactDir, "artifact.json"), JSON.stringify(artifact, null, 2));

		return c.json(artifact, 201);
	} catch (error) {
		console.error("Artifact generation error:", error);
		return c.json({ error: error instanceof Error ? error.message : "Artifact generation failed" }, 500);
	}
});

// ── GET /api/artifact/:id/file — Download render target ──
routes.get("/api/artifact/:id/file", async (c) => {
	const id = c.req.param("id");
	if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) {
		return c.json({ error: "Invalid artifact ID" }, 400);
	}

	const artifactDir = join(ARTIFACTS_DIR, id);
	try { await stat(artifactDir); } catch { return c.json({ error: "Artifact not found" }, 404); }

	const files = await readdir(artifactDir);
	const printFile = files.find((f) => f.includes("print-ready") && (f.endsWith(".png") || f.endsWith(".tiff")));
	if (!printFile) return c.json({ error: "Print file not found" }, 404);

	const buffer = await readFile(join(artifactDir, printFile));
	const contentType = printFile.endsWith(".tiff") ? "image/tiff" : "image/png";

	return new Response(new Uint8Array(buffer), {
		status: 200,
		headers: {
			"Content-Type": contentType,
			"Content-Disposition": `inline; filename="${printFile}"`,
			"Content-Length": String(buffer.length),
		},
	});
});

// ── GET /api/artifact/:id — Get artifact metadata ──
routes.get("/api/artifact/:id", async (c) => {
	const id = c.req.param("id");
	if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) {
		return c.json({ error: "Invalid artifact ID" }, 400);
	}

	const artifactPath = join(ARTIFACTS_DIR, id, "artifact.json");
	try {
		const data = await readFile(artifactPath, "utf-8");
		return c.json(JSON.parse(data));
	} catch {
		return c.json({ error: "Artifact not found" }, 404);
	}
});

// ── Design storage (filesystem-based, per-slug) ──

const DESIGNS_DIR = process.env.SWAG_DESIGNS_DIR || join(ARTIFACTS_DIR, "designs");

interface DesignMeta {
	slug: string;
	name: string;
	description: string;
	tags: string[];
	category: string;
	author: string;
	created: string;
	source: "ai-generated" | "upload" | "artifact";
	status: "draft" | "active" | "paused" | "removed";
	imageFile: string;
	products: { type: string; provider: string; sku: string; variants: string[]; retailPrice: number }[];
}

/** In-memory design index (loaded from filesystem) */
let designIndex: Map<string, DesignMeta> = new Map();
let designIndexLoaded = false;

async function loadDesignIndex() {
	designIndex.clear();
	try {
		await stat(DESIGNS_DIR);
	} catch {
		await mkdir(DESIGNS_DIR, { recursive: true });
		designIndexLoaded = true;
		return;
	}

	const categories = await readdir(DESIGNS_DIR);
	for (const cat of categories) {
		const catDir = join(DESIGNS_DIR, cat);
		try {
			const catStat = await stat(catDir);
			if (!catStat.isDirectory()) continue;
		} catch { continue; }

		const slugs = await readdir(catDir);
		for (const slug of slugs) {
			const metaPath = join(catDir, slug, "meta.json");
			try {
				const raw = await readFile(metaPath, "utf-8");
				const meta: DesignMeta = JSON.parse(raw);
				designIndex.set(slug, meta);
			} catch { /* skip invalid designs */ }
		}
	}
	designIndexLoaded = true;
}

async function ensureDesignIndex() {
	if (!designIndexLoaded) await loadDesignIndex();
}

async function saveDesignMeta(meta: DesignMeta) {
	const dir = join(DESIGNS_DIR, meta.category, meta.slug);
	await mkdir(dir, { recursive: true });
	await writeFile(join(dir, "meta.json"), JSON.stringify(meta, null, 2));
	designIndex.set(meta.slug, meta);
}

function getDesignImagePath(slug: string): string | null {
	const meta = designIndex.get(slug);
	if (!meta) return null;
	return join(DESIGNS_DIR, meta.category, meta.slug, meta.imageFile);
}

function slugify(text: string): string {
	return text.toLowerCase().trim()
		.replace(/[^\w\s-]/g, "")
		.replace(/[\s_-]+/g, "-")
		.replace(/^-+|-+$/g, "");
}

// ── POST /api/design/generate — AI-powered design generation ──
routes.post("/api/design/generate", async (c) => {
	try {
		const body = await c.req.json();
		const { concept, name, tags = [], product_type = "sticker" } = body;

		if (!concept || !name) {
			return c.json({ error: "concept and name are required" }, 400);
		}

		const geminiKey = process.env.GEMINI_API_KEY;
		if (!geminiKey) {
			return c.json({ error: "AI generation not configured. Set GEMINI_API_KEY." }, 503);
		}

		let slug = slugify(name);
		if (!slug) slug = `design-${randomUUID().slice(0, 8)}`;

		await ensureDesignIndex();
		if (designIndex.has(slug)) {
			return c.json({ error: `Design '${slug}' already exists` }, 409);
		}

		// Build AI prompt
		const stylePrompt = `A striking sticker design for "${name}".
${concept}
The design should have a clean, modern spatial-web aesthetic with interconnected
nodes, network patterns, and a collaborative/commons feel.
Colors: vibrant cyan, warm orange accents on dark background.
High contrast, suitable for vinyl sticker printing.
Square format, clean edges for die-cut sticker.`;

		// Call Gemini API for image generation
		const resp = await fetch(
			`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent?key=${geminiKey}`,
			{
				method: "POST",
				headers: { "Content-Type": "application/json" },
				body: JSON.stringify({
					contents: [{ parts: [{ text: stylePrompt }] }],
					generationConfig: { responseModalities: ["image", "text"] },
				}),
			},
		);

		if (!resp.ok) {
			const errText = await resp.text();
			return c.json({ error: `AI generation failed (${resp.status}): ${errText.slice(0, 300)}` }, 502);
		}

		const result = await resp.json();
		let imageData: string | null = null;

		for (const candidate of result.candidates || []) {
			for (const part of candidate.content?.parts || []) {
				if (part.inlineData?.data) {
					imageData = part.inlineData.data;
					break;
				}
			}
			if (imageData) break;
		}

		if (!imageData) {
			return c.json({ error: "AI did not return an image" }, 502);
		}

		// Save design
		const imageBuffer = Buffer.from(imageData, "base64");
		const category = product_type === "sticker" ? "stickers" : product_type === "shirt" || product_type === "tee" ? "apparel" : "prints";

		const meta: DesignMeta = {
			slug,
			name,
			description: concept,
			tags: tags.length ? tags : ["rspace", "ai-generated"],
			category,
			author: "ai-generated",
			created: new Date().toISOString().split("T")[0],
			source: "ai-generated",
			status: "draft",
			imageFile: `${slug}.png`,
			products: getPodProducts(product_type).map(p => ({
				type: p.type, provider: p.provider, sku: p.sku,
				variants: p.variants, retailPrice: p.retailPrice,
			})),
		};

		await saveDesignMeta(meta);
		const imgDir = join(DESIGNS_DIR, category, slug);
		await writeFile(join(imgDir, `${slug}.png`), imageBuffer);

		return c.json({
			slug,
			name,
			image_url: `/api/designs/${slug}/image`,
			status: "draft",
		}, 201);
	} catch (error) {
		console.error("[rSwag] Design generation error:", error);
		return c.json({ error: error instanceof Error ? error.message : "Generation failed" }, 500);
	}
});

// ── POST /api/design/upload — User artwork upload ──
routes.post("/api/design/upload", async (c) => {
	try {
		const formData = await c.req.formData();
		const imageFile = formData.get("image") as File | null;
		const name = (formData.get("name") as string) || "Untitled Upload";
		const description = (formData.get("description") as string) || "";
		const tagsRaw = formData.get("tags") as string | null;
		const tags = tagsRaw ? tagsRaw.split(",").map(t => t.trim()) : [];
		const productType = (formData.get("product_type") as string) || "sticker";

		if (!imageFile) return c.json({ error: "image file is required" }, 400);

		// Validate format
		const validTypes = ["image/png", "image/jpeg", "image/webp"];
		if (!validTypes.includes(imageFile.type)) {
			return c.json({ error: `Invalid format. Accepted: PNG, JPEG, WebP` }, 400);
		}

		// Validate size (max 10MB)
		if (imageFile.size > 10 * 1024 * 1024) {
			return c.json({ error: "Image too large (max 10MB)" }, 400);
		}

		const inputBuffer = Buffer.from(await imageFile.arrayBuffer());

		// Validate dimensions (min 500x500)
		const metadata = await sharp(inputBuffer).metadata();
		if (!metadata.width || !metadata.height || metadata.width < 500 || metadata.height < 500) {
			return c.json({ error: "Image too small (minimum 500x500 pixels)" }, 400);
		}

		let slug = slugify(name);
		if (!slug) slug = `upload-${randomUUID().slice(0, 8)}`;

		await ensureDesignIndex();
		// Deduplicate slug
		let finalSlug = slug;
		let counter = 1;
		while (designIndex.has(finalSlug)) {
			finalSlug = `${slug}-${counter++}`;
		}
		slug = finalSlug;

		const category = productType === "sticker" ? "stickers" : productType === "tee" || productType === "shirt" ? "apparel" : "prints";

		// Process to standard 1024x1024 PNG
		const processed = await sharp(inputBuffer)
			.resize(1024, 1024, { fit: "contain", background: { r: 255, g: 255, b: 255, alpha: 0 } })
			.png()
			.toBuffer();

		const meta: DesignMeta = {
			slug,
			name,
			description,
			tags,
			category,
			author: "upload",
			created: new Date().toISOString().split("T")[0],
			source: "upload",
			status: "draft",
			imageFile: `${slug}.png`,
			products: getPodProducts(productType).map(p => ({
				type: p.type, provider: p.provider, sku: p.sku,
				variants: p.variants, retailPrice: p.retailPrice,
			})),
		};

		await saveDesignMeta(meta);
		const imgDir = join(DESIGNS_DIR, category, slug);
		await writeFile(join(imgDir, `${slug}.png`), processed);

		return c.json({
			slug,
			name,
			image_url: `/api/designs/${slug}/image`,
			status: "draft",
		}, 201);
	} catch (error) {
		console.error("[rSwag] Upload error:", error);
		return c.json({ error: error instanceof Error ? error.message : "Upload failed" }, 500);
	}
});

// ── POST /api/design/:slug/activate — Publish draft design ──
routes.post("/api/design/:slug/activate", async (c) => {
	const slug = c.req.param("slug");
	await ensureDesignIndex();
	const meta = designIndex.get(slug);
	if (!meta) return c.json({ error: "Design not found" }, 404);

	meta.status = "active";
	await saveDesignMeta(meta);
	return c.json({ status: "activated", slug });
});

// ── DELETE /api/design/:slug — Delete draft design ──
routes.delete("/api/design/:slug", async (c) => {
	const slug = c.req.param("slug");
	await ensureDesignIndex();
	const meta = designIndex.get(slug);
	if (!meta) return c.json({ error: "Design not found" }, 404);

	if (meta.status === "active") {
		return c.json({ error: "Cannot delete active designs. Set to draft first." }, 400);
	}

	const designDir = join(DESIGNS_DIR, meta.category, slug);
	try { await rm(designDir, { recursive: true }); } catch { /* ok */ }
	designIndex.delete(slug);

	return c.json({ status: "deleted", slug });
});

// ── GET /api/designs — List designs with filtering ──
routes.get("/api/designs", async (c) => {
	await ensureDesignIndex();
	const statusFilter = c.req.query("status");
	const categoryFilter = c.req.query("category");
	const search = c.req.query("q")?.toLowerCase();

	let designs = Array.from(designIndex.values());

	if (statusFilter) designs = designs.filter(d => d.status === statusFilter);
	if (categoryFilter) designs = designs.filter(d => d.category === categoryFilter);
	if (search) {
		designs = designs.filter(d =>
			d.name.toLowerCase().includes(search) ||
			d.description.toLowerCase().includes(search) ||
			d.tags.some(t => t.toLowerCase().includes(search)),
		);
	}

	return c.json({
		designs: designs.map(d => ({
			slug: d.slug,
			name: d.name,
			description: d.description,
			category: d.category,
			tags: d.tags,
			status: d.status,
			source: d.source,
			image_url: `/api/designs/${d.slug}/image`,
			products: d.products,
			created: d.created,
		})),
	});
});

// ── GET /api/designs/:slug — Single design metadata ──
routes.get("/api/designs/:slug", async (c) => {
	const slug = c.req.param("slug");
	await ensureDesignIndex();
	const meta = designIndex.get(slug);
	if (!meta) return c.json({ error: "Design not found" }, 404);

	return c.json({
		slug: meta.slug,
		name: meta.name,
		description: meta.description,
		category: meta.category,
		tags: meta.tags,
		status: meta.status,
		source: meta.source,
		image_url: `/api/designs/${slug}/image`,
		products: meta.products,
		created: meta.created,
	});
});

// ── GET /api/designs/:slug/image — Serve design PNG ──
routes.get("/api/designs/:slug/image", async (c) => {
	await ensureDesignIndex();
	const imagePath = getDesignImagePath(c.req.param("slug"));
	if (!imagePath) return c.json({ error: "Design not found" }, 404);

	try {
		const buffer = await readFile(imagePath);
		return new Response(new Uint8Array(buffer), {
			headers: {
				"Content-Type": "image/png",
				"Cache-Control": "public, max-age=86400",
				"Content-Length": String(buffer.length),
			},
		});
	} catch {
		return c.json({ error: "Image file not found" }, 404);
	}
});

// ── GET /api/designs/:slug/mockup — Photorealistic mockup ──
routes.get("/api/designs/:slug/mockup", async (c) => {
	const slug = c.req.param("slug");
	const type = c.req.query("type") || "shirt";
	const fresh = c.req.query("fresh") === "true";

	await ensureDesignIndex();
	const imagePath = getDesignImagePath(slug);
	if (!imagePath) return c.json({ error: "Design not found" }, 404);

	try {
		const designBuffer = await readFile(imagePath);
		const meta = designIndex.get(slug);

		// Find Printful SKU if available
		const printfulProduct = meta?.products?.find(p => p.provider === "printful");
		const printfulSku = printfulProduct ? parseInt(printfulProduct.sku, 10) : undefined;

		// Build public URL for Printful API (needs accessible URL)
		const proto = c.req.header("x-forwarded-proto") || "https";
		const host = c.req.header("host") || "rswag.online";
		const designImageUrl = printfulSku ? `${proto}://${host}/api/designs/${slug}/image` : undefined;

		const mockup = await generateMockup(designBuffer, type, {
			printfulSku,
			designImageUrl,
			fresh,
		});

		return new Response(new Uint8Array(mockup), {
			headers: {
				"Content-Type": "image/png",
				"Cache-Control": "public, max-age=86400",
				"Content-Length": String(mockup.length),
			},
		});
	} catch (error) {
		console.error("[rSwag] Mockup error:", error);
		return c.json({ error: "Mockup generation failed" }, 500);
	}
});

// ── GET /api/designs/:slug/dither — Dithered PNG ──
routes.get("/api/designs/:slug/dither", async (c) => {
	const slug = c.req.param("slug");
	const algorithm = (c.req.query("algorithm") || "floyd-steinberg") as DitherAlgorithm;
	const numColors = parseInt(c.req.query("num_colors") || "8", 10);
	const customColors = c.req.query("colors")?.split(",").filter(Boolean);
	const threshold = parseInt(c.req.query("threshold") || "64", 10);
	const order = parseInt(c.req.query("order") || "3", 10);
	const format = c.req.query("format") || "image";

	if (!ALGORITHMS.includes(algorithm)) {
		return c.json({ error: `Unknown algorithm. Available: ${ALGORITHMS.join(", ")}` }, 400);
	}

	await ensureDesignIndex();
	const imagePath = getDesignImagePath(slug);
	if (!imagePath) return c.json({ error: "Design not found" }, 404);

	try {
		const imageBuffer = await readFile(imagePath);
		const result = await ditherDesign(imageBuffer, slug, {
			algorithm,
			numColors: Math.min(Math.max(numColors, 2), 32),
			customColors,
			threshold,
			order,
		});

		if (format === "json") {
			return c.json({
				slug,
				algorithm: result.algorithm,
				palette_mode: result.paletteMode,
				num_colors: result.numColors,
				colors_used: result.colorsUsed,
				cached: result.cached,
				image_url: `/api/designs/${slug}/dither?algorithm=${algorithm}&num_colors=${numColors}`,
			});
		}

		return new Response(new Uint8Array(result.buffer), {
			headers: {
				"Content-Type": "image/png",
				"Content-Disposition": `inline; filename="${slug}-${algorithm}.png"`,
				"X-Colors-Used": result.colorsUsed.join(","),
				"X-Algorithm": result.algorithm,
				"X-Cached": String(result.cached),
			},
		});
	} catch (error) {
		console.error("[rSwag] Dither error:", error);
		return c.json({ error: "Dithering failed" }, 500);
	}
});

// ── POST /api/designs/:slug/screen-print — Color separation PNGs ──
routes.post("/api/designs/:slug/screen-print", async (c) => {
	const slug = c.req.param("slug");

	await ensureDesignIndex();
	const imagePath = getDesignImagePath(slug);
	if (!imagePath) return c.json({ error: "Design not found" }, 404);

	try {
		const body = await c.req.json();
		const numColors = Math.min(Math.max(body.num_colors || 4, 2), 16);
		const algorithm = (body.algorithm || "floyd-steinberg") as DitherAlgorithm;
		const spotColors = body.spot_colors as string[] | undefined;

		const imageBuffer = await readFile(imagePath);
		const result = await generateColorSeparations(
			imageBuffer, slug, numColors, algorithm, spotColors,
		);

		const separationUrls: Record<string, string> = {};
		for (const color of result.colors) {
			separationUrls[color] = `/api/designs/${slug}/screen-print/${color}`;
		}

		// Cache separations for the GET endpoint
		screenPrintCache.set(slug, result);

		return c.json({
			slug,
			num_colors: result.colors.length,
			algorithm,
			colors: result.colors,
			composite_url: `/api/designs/${slug}/screen-print/composite`,
			separation_urls: separationUrls,
		});
	} catch (error) {
		console.error("[rSwag] Screen-print error:", error);
		return c.json({ error: "Color separation failed" }, 500);
	}
});

// Screen-print separation cache (slug → result)
const screenPrintCache = new Map<string, { composite: Buffer; separations: Map<string, Buffer>; colors: string[] }>();

// ── GET /api/designs/:slug/screen-print/:channel — Serve separation channel ──
routes.get("/api/designs/:slug/screen-print/:channel", async (c) => {
	const slug = c.req.param("slug");
	const channel = c.req.param("channel");

	const cached = screenPrintCache.get(slug);
	if (!cached) return c.json({ error: "Run POST screen-print first" }, 404);

	let buffer: Buffer;
	if (channel === "composite") {
		buffer = cached.composite;
	} else {
		const sep = cached.separations.get(channel.toUpperCase());
		if (!sep) return c.json({ error: `Channel ${channel} not found` }, 404);
		buffer = sep;
	}

	return new Response(new Uint8Array(buffer), {
		headers: {
			"Content-Type": "image/png",
			"Content-Disposition": `inline; filename="${slug}-${channel}.png"`,
		},
	});
});

// ── GET /api/storefront — Storefront product listing ──
routes.get("/api/storefront", async (c) => {
	await ensureDesignIndex();

	const products: StorefrontProduct[] = [];

	for (const meta of designIndex.values()) {
		if (meta.status !== "active") continue;

		for (const prod of meta.products) {
			products.push({
				slug: `${meta.slug}-${prod.type}`,
				designSlug: meta.slug,
				name: `${meta.name} ${prod.type}`,
				description: meta.description,
				category: meta.category,
				productType: prod.type,
				imageUrl: `/api/designs/${meta.slug}/image`,
				basePrice: prod.retailPrice,
				variants: prod.variants,
				provider: prod.provider,
				sku: prod.sku,
				isActive: true,
			});
		}
	}

	return c.json({ products });
});

// ── GET /api/storefront/:slug — Single storefront product ──
routes.get("/api/storefront/:slug", async (c) => {
	const slug = c.req.param("slug");
	await ensureDesignIndex();

	// Parse "designSlug-productType" format
	for (const meta of designIndex.values()) {
		for (const prod of meta.products) {
			const prodSlug = `${meta.slug}-${prod.type}`;
			if (prodSlug === slug) {
				return c.json({
					slug: prodSlug,
					designSlug: meta.slug,
					name: `${meta.name} ${prod.type}`,
					description: meta.description,
					category: meta.category,
					productType: prod.type,
					imageUrl: `/api/designs/${meta.slug}/image`,
					mockupUrl: `/api/designs/${meta.slug}/mockup?type=${prod.type}`,
					basePrice: prod.retailPrice,
					variants: prod.variants,
					provider: prod.provider,
					sku: prod.sku,
					isActive: meta.status === "active",
				});
			}
		}
	}

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

// ── POST /api/fulfill/submit — Submit rCart order to POD provider ──
routes.post("/api/fulfill/submit", async (c) => {
	try {
		const body = await c.req.json() as FulfillmentRequest;
		if (!body.orderId || !body.items?.length || !body.recipient) {
			return c.json({ error: "orderId, items, and recipient are required" }, 400);
		}

		const results = await submitFulfillment(body);
		return c.json({ results }, 201);
	} catch (error) {
		console.error("[rSwag] Fulfillment error:", error);
		return c.json({ error: error instanceof Error ? error.message : "Fulfillment failed" }, 500);
	}
});

// ── POST /api/webhooks/printful — Printful shipment webhook ──
routes.post("/api/webhooks/printful", async (c) => {
	try {
		const body = await c.req.json();
		const event = parsePrintfulWebhook(body);
		if (!event) return c.json({ error: "Invalid webhook" }, 400);

		console.log(`[rSwag] Printful webhook: ${event.type} for order ${event.orderId}`);

		// TODO: push tracking update to rCart order doc
		return c.json({ received: true, type: event.type });
	} catch {
		return c.json({ error: "Webhook processing failed" }, 500);
	}
});

// ── POST /api/webhooks/prodigi — Prodigi shipment webhook ──
routes.post("/api/webhooks/prodigi", async (c) => {
	try {
		const body = await c.req.json();
		const event = parseProdigiWebhook(body);
		if (!event) return c.json({ error: "Invalid webhook" }, 400);

		console.log(`[rSwag] Prodigi webhook: ${event.type} for order ${event.orderId}`);

		// TODO: push tracking update to rCart order doc
		return c.json({ received: true, type: event.type });
	} catch {
		return c.json({ error: "Webhook processing failed" }, 500);
	}
});

// ── GET /api/orders/:id/tracking — Tracking info lookup ──
routes.get("/api/orders/:id/tracking", async (c) => {
	const orderId = c.req.param("id");
	const fulfillments = getFulfillmentStatus(orderId);
	if (!fulfillments) return c.json({ error: "Order not found" }, 404);

	const tracking = [];
	for (const f of fulfillments) {
		const info = await getTrackingInfo(f.provider, f.providerOrderId);
		tracking.push({
			provider: f.provider,
			providerOrderId: f.providerOrderId,
			status: f.status,
			tracking: info,
		});
	}

	return c.json({ orderId, fulfillments: tracking });
});

// ── GET /api/dither/algorithms — List available dithering algorithms ──
routes.get("/api/dither/algorithms", (c) => {
	return c.json({ algorithms: ALGORITHMS });
});

// ── POST /api/admin/designs/sync — Reload designs from storage ──
routes.post("/api/admin/designs/sync", async (c) => {
	designIndexLoaded = false;
	await loadDesignIndex();
	return c.json({ synced: true, count: designIndex.size });
});

// ── PUT /api/admin/products/:slug/override — Price/visibility override ──
routes.put("/api/admin/products/:slug/override", async (c) => {
	const slug = c.req.param("slug");
	await ensureDesignIndex();
	const meta = designIndex.get(slug);
	if (!meta) return c.json({ error: "Design not found" }, 404);

	const body = await c.req.json();
	if (body.status) meta.status = body.status;
	if (body.products) meta.products = body.products;
	await saveDesignMeta(meta);

	return c.json({ updated: true, slug });
});

// ── GET /api/admin/analytics/summary — Sales metrics ──
routes.get("/api/admin/analytics/summary", async (c) => {
	await ensureDesignIndex();

	const totalDesigns = designIndex.size;
	const activeDesigns = Array.from(designIndex.values()).filter(d => d.status === "active").length;
	const draftDesigns = Array.from(designIndex.values()).filter(d => d.status === "draft").length;

	// Count artifacts
	let totalArtifacts = 0;
	try {
		const dirs = await readdir(ARTIFACTS_DIR);
		totalArtifacts = dirs.filter(d => d.match(/^[0-9a-f-]{36}$/)).length;
	} catch { /* ok */ }

	return c.json({
		totalDesigns,
		activeDesigns,
		draftDesigns,
		totalArtifacts,
		designsByCategory: Array.from(designIndex.values()).reduce((acc, d) => {
			acc[d.category] = (acc[d.category] || 0) + 1;
			return acc;
		}, {} as Record<string, number>),
		designsBySource: Array.from(designIndex.values()).reduce((acc, d) => {
			acc[d.source] = (acc[d.source] || 0) + 1;
			return acc;
		}, {} as Record<string, number>),
	});
});

// ── CRUD: Catalog (Automerge) ──

routes.get("/api/catalog", (c) => {
    if (!_syncServer) return c.json({ designs: [] });
    const space = c.req.param("space") || "demo";
    const doc = ensureSwagDoc(space);
    return c.json({ designs: Object.values(doc.designs || {}) });
});

routes.post("/api/catalog", async (c) => {
    const authToken = extractToken(c.req.raw.headers);
    if (!authToken) return c.json({ error: "Authentication required" }, 401);
    try { await verifyToken(authToken); } catch { return c.json({ error: "Invalid token" }, 401); }
    if (!_syncServer) return c.json({ error: "Not initialized" }, 503);
    const space = c.req.param("space") || "demo";
    const { title, productType = "sticker", description, tags = [] } = await c.req.json();
    if (!title) return c.json({ error: "title required" }, 400);
    const id = crypto.randomUUID();
    const docId = swagDocId(space);
    ensureSwagDoc(space);
    _syncServer.changeDoc<SwagDoc>(docId, `add design ${id}`, (d) => {
        d.designs[id] = { id, title, productType, artifactId: null, source: 'upload', status: 'draft', imageUrl: null, products: [], slug: null, description: description || null, tags, createdBy: null, createdAt: Date.now(), updatedAt: Date.now() };
    });
    const updated = _syncServer.getDoc<SwagDoc>(docId)!;
    return c.json(updated.designs[id], 201);
});

// ── Page route: swag designer ──
routes.get("/", (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = c.get("effectiveSpace") || space;
	return c.html(renderShell({
		title: `Swag Designer | rSpace`,
		moduleId: "rswag",
		spaceSlug: space,
		modules: getModuleInfoList(),
		theme: "dark",
		body: `<folk-swag-designer space="${space}"></folk-swag-designer>`,
		scripts: `<script type="module" src="/modules/rswag/folk-swag-designer.js?v=4"></script>
			<script type="module" src="/modules/rswag/folk-revenue-sankey.js?v=1"></script>`,
		styles: `<link rel="stylesheet" href="/modules/rswag/swag.css">`,
	}));
});

export function getRecentDesignsForMI(space: string, limit = 5): { id: string; title: string; productType: string; status: string; createdAt: number }[] {
    if (!_syncServer) return [];
    const doc = _syncServer.getDoc<SwagDoc>(swagDocId(space));
    if (!doc?.designs) return [];
    return Object.values(doc.designs)
        .sort((a, b) => b.createdAt - a.createdAt)
        .slice(0, limit)
        .map(d => ({ id: d.id, title: d.title, productType: d.productType, status: d.status, createdAt: d.createdAt }));
}

export const swagModule: RSpaceModule = {
	id: "rswag",
	name: "rSwag",
	icon: "🎨",
	description: "Design print-ready swag: stickers, posters, tees",
	scoping: { defaultScope: 'global', userConfigurable: true },
	routes,
	docSchemas: [{ pattern: '{space}:swag:designs', description: 'Design catalog per space', init: swagSchema.init }],
	async onInit(ctx) { _syncServer = ctx.syncServer; },
	landingPage: renderLanding,
	standaloneDomain: "rswag.online",
	feeds: [
		{
			id: "designs",
			name: "Designs",
			kind: "resource",
			description: "Print-ready design artifacts for stickers, posters, and tees",
			filterable: true,
		},
		{
			id: "merch-orders",
			name: "Merch Orders",
			kind: "economic",
			description: "Merchandise order events and fulfillment tracking",
		},
	],
	acceptsFeeds: ["economic", "resource"],
	outputPaths: [
		{ path: "merchandise", name: "Merchandise", icon: "👕", description: "Print-ready swag designs" },
	],
};
