/**
 * Inbox module — collaborative email with multisig approval.
 *
 * Shared mailboxes with role-based access, threaded comments,
 * and Gnosis Safe multisig approval for outgoing emails.
 */

import { Hono } from "hono";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { sql } from "../../shared/db/pool";
import { renderShell } from "../../server/shell";
import { getModuleInfoList } from "../../shared/module";
import type { RSpaceModule } from "../../shared/module";
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
import { renderLanding } from "./landing";

const routes = new Hono();

// ── DB initialization ──
const SCHEMA_SQL = readFileSync(resolve(import.meta.dir, "db/schema.sql"), "utf-8");

async function initDB() {
	try {
		await sql.unsafe(SCHEMA_SQL);
		console.log("[Inbox] DB schema initialized");
	} catch (e) {
		console.error("[Inbox] DB init error:", e);
	}
}

initDB();

// ── Helper: get or create user by DID ──
async function getOrCreateUser(did: string, username?: string) {
	const rows = await sql.unsafe(
		`INSERT INTO rinbox.users (did, username) VALUES ($1, $2)
		 ON CONFLICT (did) DO UPDATE SET username = COALESCE($2, rinbox.users.username)
		 RETURNING *`,
		[did, username || null]
	);
	return rows[0];
}

// ── Mailboxes API ──

// GET /api/mailboxes — list mailboxes
routes.get("/api/mailboxes", async (c) => {
	const { workspace } = c.req.query();
	let rows;
	if (workspace) {
		rows = await sql.unsafe(
			`SELECT m.* FROM rinbox.mailboxes m
			 JOIN rinbox.workspaces w ON w.id = m.workspace_id
			 WHERE w.slug = $1 ORDER BY m.created_at DESC`,
			[workspace]
		);
	} else {
		rows = await sql.unsafe(
			"SELECT * FROM rinbox.mailboxes ORDER BY created_at DESC LIMIT 50"
		);
	}
	return c.json({ mailboxes: rows });
});

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

	const body = await c.req.json();
	const { slug, name, email, description, visibility = "members_only", imap_user, imap_password } = body;
	if (!slug || !name || !email) return c.json({ error: "slug, name, email required" }, 400);
	if (!/^[a-z0-9-]+$/.test(slug)) return c.json({ error: "Invalid slug" }, 400);

	try {
		const rows = await sql.unsafe(
			`INSERT INTO rinbox.mailboxes (slug, name, email, description, visibility, owner_did, imap_user)
			 VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`,
			[slug, name, email, description || null, visibility, claims.sub, imap_user || null]
		);
		return c.json(rows[0], 201);
	} catch (e: any) {
		if (e.code === "23505") return c.json({ error: "Mailbox already exists" }, 409);
		throw e;
	}
});

// GET /api/mailboxes/:slug — mailbox detail
routes.get("/api/mailboxes/:slug", async (c) => {
	const slug = c.req.param("slug");
	const rows = await sql.unsafe("SELECT * FROM rinbox.mailboxes WHERE slug = $1", [slug]);
	if (rows.length === 0) return c.json({ error: "Mailbox not found" }, 404);

	// Get thread count
	const counts = await sql.unsafe(
		`SELECT status, count(*) as cnt FROM rinbox.threads WHERE mailbox_id = $1 GROUP BY status`,
		[rows[0].id]
	);
	const threadCounts: Record<string, number> = {};
	for (const row of counts) threadCounts[row.status] = parseInt(row.cnt);

	return c.json({ ...rows[0], threadCounts });
});

// ── Threads API ──

// GET /api/mailboxes/:slug/threads — list threads
routes.get("/api/mailboxes/:slug/threads", async (c) => {
	const slug = c.req.param("slug");
	const { status, search, limit = "50", offset = "0" } = c.req.query();

	const mailbox = await sql.unsafe("SELECT id FROM rinbox.mailboxes WHERE slug = $1", [slug]);
	if (mailbox.length === 0) return c.json({ error: "Mailbox not found" }, 404);

	const conditions = ["mailbox_id = $1"];
	const params: any[] = [mailbox[0].id];
	let idx = 2;

	if (status) {
		conditions.push(`status = $${idx}`);
		params.push(status);
		idx++;
	}
	if (search) {
		conditions.push(`(subject ILIKE $${idx} OR from_address ILIKE $${idx})`);
		params.push(`%${search}%`);
		idx++;
	}

	const where = conditions.join(" AND ");
	const rows = await sql.unsafe(
		`SELECT t.*, (SELECT count(*) FROM rinbox.comments WHERE thread_id = t.id) as comment_count
		 FROM rinbox.threads t WHERE ${where}
		 ORDER BY t.received_at DESC
		 LIMIT ${Math.min(parseInt(limit), 100)} OFFSET ${parseInt(offset) || 0}`,
		params
	);

	return c.json({ threads: rows });
});

// GET /api/threads/:id — thread detail with comments
routes.get("/api/threads/:id", async (c) => {
	const id = c.req.param("id");
	const rows = await sql.unsafe("SELECT * FROM rinbox.threads WHERE id = $1", [id]);
	if (rows.length === 0) return c.json({ error: "Thread not found" }, 404);

	const comments = await sql.unsafe(
		`SELECT c.*, u.username, u.did as author_did
		 FROM rinbox.comments c
		 LEFT JOIN rinbox.users u ON u.id = c.author_id
		 WHERE c.thread_id = $1 ORDER BY c.created_at ASC`,
		[id]
	);

	return c.json({ ...rows[0], comments });
});

// PATCH /api/threads/:id — update thread metadata
routes.patch("/api/threads/:id", async (c) => {
	const id = c.req.param("id");
	const body = await c.req.json();
	const allowed = ["status", "is_read", "is_starred", "tags", "assigned_to"];
	const updates: string[] = [];
	const params: any[] = [];
	let idx = 1;

	for (const key of allowed) {
		if (key in body) {
			const col = key === "tags" ? "tags" : key;
			updates.push(`${col} = $${idx}`);
			params.push(key === "tags" ? body[key] : body[key]);
			idx++;
		}
	}

	if (updates.length === 0) return c.json({ error: "No valid fields" }, 400);

	params.push(id);
	const rows = await sql.unsafe(
		`UPDATE rinbox.threads SET ${updates.join(", ")} WHERE id = $${idx} RETURNING *`,
		params
	);
	if (rows.length === 0) return c.json({ error: "Thread not found" }, 404);
	return c.json(rows[0]);
});

// POST /api/threads/:id/comments — add comment
routes.post("/api/threads/:id/comments", async (c) => {
	const token = extractToken(c.req.raw.headers);
	if (!token) return c.json({ error: "Authentication required" }, 401);
	let claims;
	try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }

	const threadId = c.req.param("id");
	const body = await c.req.json();
	const { text, mentions } = body;
	if (!text) return c.json({ error: "text required" }, 400);

	// Ensure thread exists
	const thread = await sql.unsafe("SELECT id FROM rinbox.threads WHERE id = $1", [threadId]);
	if (thread.length === 0) return c.json({ error: "Thread not found" }, 404);

	const user = await getOrCreateUser(claims.sub, claims.username);

	const rows = await sql.unsafe(
		`INSERT INTO rinbox.comments (thread_id, author_id, body, mentions)
		 VALUES ($1, $2, $3, $4) RETURNING *`,
		[threadId, user.id, text, mentions || []]
	);
	return c.json(rows[0], 201);
});

// ── Approvals API ──

// GET /api/approvals — list pending approvals
routes.get("/api/approvals", async (c) => {
	const { mailbox, status = "PENDING" } = c.req.query();
	let rows;
	if (mailbox) {
		const mb = await sql.unsafe("SELECT id FROM rinbox.mailboxes WHERE slug = $1", [mailbox]);
		if (mb.length === 0) return c.json({ error: "Mailbox not found" }, 404);
		rows = await sql.unsafe(
			`SELECT a.*, (SELECT count(*) FROM rinbox.approval_signatures WHERE approval_id = a.id) as signature_count
			 FROM rinbox.approvals a WHERE a.mailbox_id = $1 AND a.status = $2
			 ORDER BY a.created_at DESC`,
			[mb[0].id, status]
		);
	} else {
		rows = await sql.unsafe(
			`SELECT a.*, (SELECT count(*) FROM rinbox.approval_signatures WHERE approval_id = a.id) as signature_count
			 FROM rinbox.approvals a WHERE a.status = $1 ORDER BY a.created_at DESC LIMIT 50`,
			[status]
		);
	}
	return c.json({ approvals: rows });
});

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

	const body = await c.req.json();
	const { mailbox_slug, thread_id, subject, body_text, to_addresses } = body;
	if (!mailbox_slug || !subject) return c.json({ error: "mailbox_slug and subject required" }, 400);

	const mb = await sql.unsafe("SELECT * FROM rinbox.mailboxes WHERE slug = $1", [mailbox_slug]);
	if (mb.length === 0) return c.json({ error: "Mailbox not found" }, 404);

	const user = await getOrCreateUser(claims.sub, claims.username);

	const rows = await sql.unsafe(
		`INSERT INTO rinbox.approvals (mailbox_id, thread_id, author_id, subject, body_text, to_addresses, required_signatures)
		 VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`,
		[mb[0].id, thread_id || null, user.id, subject, body_text || null, JSON.stringify(to_addresses || []), mb[0].approval_threshold || 1]
	);
	return c.json(rows[0], 201);
});

// POST /api/approvals/:id/sign — sign an approval
routes.post("/api/approvals/:id/sign", async (c) => {
	const token = extractToken(c.req.raw.headers);
	if (!token) return c.json({ error: "Authentication required" }, 401);
	let claims;
	try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }

	const id = c.req.param("id");
	const body = await c.req.json();
	const { vote = "APPROVE" } = body;
	if (!["APPROVE", "REJECT"].includes(vote)) return c.json({ error: "Invalid vote" }, 400);

	const approval = await sql.unsafe("SELECT * FROM rinbox.approvals WHERE id = $1", [id]);
	if (approval.length === 0) return c.json({ error: "Approval not found" }, 404);
	if (approval[0].status !== "PENDING") return c.json({ error: "Approval not pending" }, 400);

	const user = await getOrCreateUser(claims.sub, claims.username);

	await sql.unsafe(
		`INSERT INTO rinbox.approval_signatures (approval_id, signer_id, vote)
		 VALUES ($1, $2, $3)
		 ON CONFLICT (approval_id, signer_id) DO UPDATE SET vote = $3, signed_at = NOW()`,
		[id, user.id, vote]
	);

	// Check if threshold reached
	const sigs = await sql.unsafe(
		"SELECT count(*) as cnt FROM rinbox.approval_signatures WHERE approval_id = $1 AND vote = 'APPROVE'",
		[id]
	);
	const approveCount = parseInt(sigs[0].cnt);

	if (approveCount >= approval[0].required_signatures) {
		await sql.unsafe(
			"UPDATE rinbox.approvals SET status = 'APPROVED', resolved_at = NOW() WHERE id = $1",
			[id]
		);
		return c.json({ ok: true, status: "APPROVED", signatures: approveCount });
	}

	// Check for rejection (more rejects than possible remaining approvals)
	const rejects = await sql.unsafe(
		"SELECT count(*) as cnt FROM rinbox.approval_signatures WHERE approval_id = $1 AND vote = 'REJECT'",
		[id]
	);
	const rejectCount = parseInt(rejects[0].cnt);
	if (rejectCount > 0) {
		await sql.unsafe(
			"UPDATE rinbox.approvals SET status = 'REJECTED', resolved_at = NOW() WHERE id = $1",
			[id]
		);
		return c.json({ ok: true, status: "REJECTED", signatures: approveCount });
	}

	return c.json({ ok: true, status: "PENDING", signatures: approveCount, required: approval[0].required_signatures });
});

// ── Workspaces API ──

// GET /api/workspaces
routes.get("/api/workspaces", async (c) => {
	const rows = await sql.unsafe("SELECT * FROM rinbox.workspaces ORDER BY created_at DESC LIMIT 50");
	return c.json({ workspaces: rows });
});

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

	const body = await c.req.json();
	const { slug, name, description } = body;
	if (!slug || !name) return c.json({ error: "slug and name required" }, 400);

	try {
		const rows = await sql.unsafe(
			`INSERT INTO rinbox.workspaces (slug, name, description, owner_did)
			 VALUES ($1, $2, $3, $4) RETURNING *`,
			[slug, name, description || null, claims.sub]
		);
		return c.json(rows[0], 201);
	} catch (e: any) {
		if (e.code === "23505") return c.json({ error: "Workspace already exists" }, 409);
		throw e;
	}
});

// GET /api/health
routes.get("/api/health", (c) => c.json({ ok: true, imapSync: IMAP_HOST !== "" }));

// GET /api/sync-status — show IMAP sync state per mailbox
routes.get("/api/sync-status", async (c) => {
	const rows = await sql.unsafe(
		`SELECT s.*, m.slug, m.name, m.email
		 FROM rinbox.sync_state s
		 JOIN rinbox.mailboxes m ON m.id = s.mailbox_id
		 ORDER BY s.last_sync_at DESC NULLS LAST`
	);
	return c.json({ syncStates: rows });
});

// ── IMAP Sync Worker ──
const IMAP_HOST = process.env.IMAP_HOST || "";
const IMAP_PORT = parseInt(process.env.IMAP_PORT || "993");
const IMAP_TLS_REJECT = process.env.IMAP_TLS_REJECT_UNAUTHORIZED !== "false";
const SYNC_INTERVAL = 30_000; // 30 seconds

async function syncMailbox(mailbox: any) {
	let ImapFlow: any;
	let simpleParser: any;
	try {
		ImapFlow = (await import("imapflow")).ImapFlow;
		simpleParser = (await import("mailparser")).simpleParser;
	} catch {
		console.error("[Inbox] imapflow/mailparser not installed — skipping IMAP sync");
		return;
	}

	const host = mailbox.imap_host || IMAP_HOST;
	const port = mailbox.imap_port || IMAP_PORT;
	const user = mailbox.imap_user;
	if (!host || !user) return;

	// Get or create sync state
	let syncState = (await sql.unsafe(
		"SELECT * FROM rinbox.sync_state WHERE mailbox_id = $1",
		[mailbox.id]
	))[0];

	if (!syncState) {
		const rows = await sql.unsafe(
			"INSERT INTO rinbox.sync_state (mailbox_id) VALUES ($1) RETURNING *",
			[mailbox.id]
		);
		syncState = rows[0];
	}

	const client = new ImapFlow({
		host,
		port,
		secure: port === 993,
		auth: { user, pass: process.env[`IMAP_PASS_${mailbox.slug.toUpperCase().replace(/-/g, "_")}`] || "" },
		tls: { rejectUnauthorized: IMAP_TLS_REJECT },
		logger: false,
	});

	try {
		await client.connect();
		const lock = await client.getMailboxLock("INBOX");

		try {
			const status = client.mailbox;
			const uidValidity = status?.uidValidity;

			// UID validity changed — need full resync
			if (syncState.uid_validity && uidValidity && syncState.uid_validity !== uidValidity) {
				await sql.unsafe(
					"UPDATE rinbox.sync_state SET last_uid = 0, uid_validity = $1 WHERE mailbox_id = $2",
					[uidValidity, mailbox.id]
				);
				syncState.last_uid = 0;
			}

			// Fetch messages newer than last synced UID
			const lastUid = syncState.last_uid || 0;
			const range = lastUid > 0 ? `${lastUid + 1}:*` : "1:*";
			let maxUid = lastUid;
			let count = 0;

			for await (const msg of client.fetch(range, {
				envelope: true,
				source: true,
				uid: true,
			})) {
				if (msg.uid <= lastUid) continue;
				if (count >= 100) break; // Batch limit

				try {
					const parsed = await simpleParser(msg.source);
					const fromAddr = parsed.from?.value?.[0]?.address || msg.envelope?.from?.[0]?.address || "";
					const fromName = parsed.from?.value?.[0]?.name || msg.envelope?.from?.[0]?.name || "";
					const subject = parsed.subject || msg.envelope?.subject || "(no subject)";
					const toAddrs = (parsed.to?.value || []).map((a: any) => ({ address: a.address, name: a.name }));
					const ccAddrs = (parsed.cc?.value || []).map((a: any) => ({ address: a.address, name: a.name }));
					const messageId = parsed.messageId || msg.envelope?.messageId || null;
					const hasAttachments = (parsed.attachments?.length || 0) > 0;

					await sql.unsafe(
						`INSERT INTO rinbox.threads (mailbox_id, message_id, subject, from_address, from_name, to_addresses, cc_addresses, body_text, body_html, has_attachments, received_at)
						 VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7::jsonb, $8, $9, $10, $11)
						 ON CONFLICT DO NOTHING`,
						[
							mailbox.id,
							messageId,
							subject,
							fromAddr,
							fromName,
							JSON.stringify(toAddrs),
							JSON.stringify(ccAddrs),
							parsed.text || null,
							parsed.html || null,
							hasAttachments,
							parsed.date || new Date(),
						]
					);
					count++;
				} catch (parseErr) {
					console.error(`[Inbox] Parse error UID ${msg.uid}:`, parseErr);
				}

				if (msg.uid > maxUid) maxUid = msg.uid;
			}

			// Update sync state
			await sql.unsafe(
				"UPDATE rinbox.sync_state SET last_uid = $1, uid_validity = $2, last_sync_at = NOW(), error = NULL WHERE mailbox_id = $3",
				[maxUid, uidValidity || null, mailbox.id]
			);

			if (count > 0) console.log(`[Inbox] Synced ${count} messages for ${mailbox.email}`);
		} finally {
			lock.release();
		}

		await client.logout();
	} catch (e: any) {
		console.error(`[Inbox] IMAP sync error for ${mailbox.email}:`, e.message);
		await sql.unsafe(
			"UPDATE rinbox.sync_state SET error = $1, last_sync_at = NOW() WHERE mailbox_id = $2",
			[e.message, mailbox.id]
		).catch(() => {});
	}
}

async function runSyncLoop() {
	if (!IMAP_HOST) {
		console.log("[Inbox] IMAP_HOST not set — IMAP sync disabled");
		return;
	}
	console.log(`[Inbox] IMAP sync enabled — polling every ${SYNC_INTERVAL / 1000}s`);

	const doSync = async () => {
		try {
			const mailboxes = await sql.unsafe(
				"SELECT * FROM rinbox.mailboxes WHERE imap_user IS NOT NULL"
			);
			for (const mb of mailboxes) {
				await syncMailbox(mb);
			}
		} catch (e) {
			console.error("[Inbox] Sync loop error:", e);
		}
	};

	// Initial sync after 5s delay
	setTimeout(doSync, 5000);
	// Recurring sync
	setInterval(doSync, SYNC_INTERVAL);
}

// Start IMAP sync in background
runSyncLoop();

// ── About / use-cases landing ──
routes.get("/about", (c) => {
	const space = c.req.param("space") || "demo";
	return c.html(renderShell({
		title: `Multi-Sig Inbox — rInbox | rSpace`,
		moduleId: "rinbox",
		spaceSlug: space,
		modules: getModuleInfoList(),
		theme: "dark",
		body: `
<div style="max-width:56rem;margin:0 auto;padding:2rem 1.5rem;">
  <h1 style="font-size:2rem;font-weight:bold;text-align:center;margin-bottom:0.5rem;">What can you do with a multi-sig inbox?</h1>
  <p style="color:#94a3b8;text-align:center;margin-bottom:2.5rem;max-width:36rem;margin-left:auto;margin-right:auto;">
    When outbound email requires collective approval, entirely new coordination patterns become possible.
  </p>
  <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1.5rem;">
    <div style="background:rgba(30,41,59,.5);border:1px solid rgba(51,65,85,.5);border-radius:1rem;padding:1.5rem;">
      <h3 style="font-size:1.1rem;font-weight:600;margin-bottom:0.5rem;color:#22d3ee;">Governance &amp; Resolutions</h3>
      <p style="color:#94a3b8;font-size:.875rem;line-height:1.6;">Board decisions require 3-of-5 signers before the email sends. The message is the vote &mdash; no separate tooling needed.</p>
    </div>
    <div style="background:rgba(30,41,59,.5);border:1px solid rgba(51,65,85,.5);border-radius:1rem;padding:1.5rem;">
      <h3 style="font-size:1.1rem;font-weight:600;margin-bottom:0.5rem;color:#a78bfa;">Escrow &amp; Conditional Release</h3>
      <p style="color:#94a3b8;font-size:.875rem;line-height:1.6;">Hold sensitive documents in an inbox that only unlocks when N parties agree. Mediation where neither side can act alone.</p>
    </div>
    <div style="background:rgba(30,41,59,.5);border:1px solid rgba(51,65,85,.5);border-radius:1rem;padding:1.5rem;">
      <h3 style="font-size:1.1rem;font-weight:600;margin-bottom:0.5rem;color:#f87171;">Whistleblower Coordination</h3>
      <p style="color:#94a3b8;font-size:.875rem;line-height:1.6;">Evidence requires M-of-N co-signers before release. Dead man&rsquo;s switch if a signer goes silent. Nobody goes first alone.</p>
    </div>
    <div style="background:rgba(30,41,59,.5);border:1px solid rgba(51,65,85,.5);border-radius:1rem;padding:1.5rem;">
      <h3 style="font-size:1.1rem;font-weight:600;margin-bottom:0.5rem;color:#4ade80;">Social Key Recovery</h3>
      <p style="color:#94a3b8;font-size:.875rem;line-height:1.6;">Lost access? 3-of-5 trusted contacts co-sign your restoration. No phone number, no backup email &mdash; a trust network.</p>
    </div>
    <div style="background:rgba(30,41,59,.5);border:1px solid rgba(51,65,85,.5);border-radius:1rem;padding:1.5rem;">
      <h3 style="font-size:1.1rem;font-weight:600;margin-bottom:0.5rem;color:#fbbf24;">Tamper-Proof Audit Trails</h3>
      <p style="color:#94a3b8;font-size:.875rem;line-height:1.6;">Every email read and sent is co-signed. Cryptographic proof of who approved what, when. Built for compliance.</p>
    </div>
    <div style="background:rgba(30,41,59,.5);border:1px solid rgba(51,65,85,.5);border-radius:1rem;padding:1.5rem;">
      <h3 style="font-size:1.1rem;font-weight:600;margin-bottom:0.5rem;color:#60a5fa;">Treasury &amp; Payments</h3>
      <p style="color:#94a3b8;font-size:.875rem;line-height:1.6;">Invoice arrives, 2-of-3 finance team co-sign the reply authorizing payment. Bridges email to on-chain wallets.</p>
    </div>
  </div>
  <div style="text-align:center;margin-top:2.5rem;">
    <a href="/${space}/rinbox" style="display:inline-block;padding:0.75rem 2rem;background:#0891b2;color:white;border-radius:0.75rem;text-decoration:none;font-weight:600;">Open Inbox</a>
    <a href="https://rinbox.online" style="display:inline-block;padding:0.75rem 2rem;margin-left:1rem;border:1px solid rgba(51,65,85,.5);color:#94a3b8;border-radius:0.75rem;text-decoration:none;font-weight:600;">rinbox.online</a>
  </div>
</div>`,
		scripts: "",
		styles: "",
	}));
});

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

export const inboxModule: RSpaceModule = {
	id: "rinbox",
	name: "rInbox",
	icon: "📨",
	description: "Collaborative email with multisig approval",
	scoping: { defaultScope: 'space', userConfigurable: false },
	routes,
	landingPage: renderLanding,
	standaloneDomain: "rinbox.online",
	feeds: [
		{
			id: "messages",
			name: "Messages",
			kind: "data",
			description: "Email threads and messages across shared mailboxes",
			filterable: true,
		},
		{
			id: "notifications",
			name: "Notifications",
			kind: "attention",
			description: "New mail alerts, approval requests, and mention notifications",
		},
	],
	acceptsFeeds: ["data"],
};
