import { Hono } from "hono";
import type { FlowKind, FeedDefinition } from "../lib/layer-types";
export type { FeedDefinition } from "../lib/layer-types";
import type { SyncServer } from "../server/local-first/sync-server";

// ── Module Scoping ──

export type ModuleScope = 'space' | 'global';

export interface ModuleScoping {
	/** Whether the module's data lives per-space or globally by default */
	defaultScope: ModuleScope;
	/** Whether space owners can override the default scope */
	userConfigurable: boolean;
}

// ── Lifecycle Context ──

export interface SpaceLifecycleContext {
	spaceSlug: string;
	ownerDID: string | null;
	enabledModules: string[];
	syncServer: SyncServer;
}

// ── Doc Schema (for Automerge document types a module manages) ──

export interface DocSchema<T = unknown> {
	/** Document ID pattern, e.g. '{space}:notes:notebooks:{notebookId}' */
	pattern: string;
	/** Human-readable description */
	description: string;
	/** Factory to create a fresh empty document */
	init: () => T;
}

/** Info for a sub-page shown on bare-domain instead of the functional app. */
export interface SubPageInfo {
	/** URL segment (e.g. "thread", "flow", "crm") */
	path: string;
	/** Display title (e.g. "Thread Builder") */
	title: string;
	/** Emoji icon */
	icon: string;
	/** Short tagline shown as a pill above the title */
	tagline: string;
	/** 1-2 sentence description */
	description: string;
	/** Optional feature cards for a grid section */
	features?: Array<{ icon: string; title: string; text: string }>;
	/** Optional: fully custom body HTML (replaces generic template) */
	bodyHTML?: () => string;
}

// ── Per-Module Settings ──

export type ModuleSettingType = 'string' | 'boolean' | 'select' | 'notebook-id' | 'password';

export interface ModuleSettingField {
	/** Storage key */
	key: string;
	/** Display label */
	label: string;
	/** Field type */
	type: ModuleSettingType;
	/** Help text */
	description?: string;
	/** Default value */
	default?: string | boolean;
	/** Options for 'select' type */
	options?: Array<{ value: string; label: string }>;
}

/** An onboarding call-to-action shown when a module has no data. */
export interface OnboardingAction {
	/** Button label, e.g. "Import Calendar (.ics)" */
	label: string;
	/** Emoji icon */
	icon: string;
	/** Short description, e.g. "Upload an ICS file from Google Calendar, Outlook, or Apple" */
	description: string;
	/** Action type: link navigates, upload opens file picker, create navigates to module */
	type: 'link' | 'upload' | 'create';
	/** For 'link'/'create': URL with {space} placeholder */
	href?: string;
	/** For 'upload': file accept filter + POST endpoint */
	upload?: { accept: string; endpoint: string };
}

/** A browsable content type that a module produces. */
export interface OutputPath {
	/** URL segment: "notebooks" */
	path: string;
	/** Display name: "Notebooks" */
	name: string;
	/** Emoji: "📓" */
	icon: string;
	/** Short description: "Rich-text collaborative notebooks" */
	description: string;
}

/**
 * The contract every rSpace module must implement.
 *
 * A module is a self-contained feature area (books, pubs, cart, canvas, etc.)
 * that exposes Hono routes and metadata. The shell mounts these routes under
 * `/:space/{moduleId}` in unified mode. In standalone mode, the module's own
 * `standalone.ts` mounts them at the root with a minimal shell.
 */
export interface RSpaceModule {
	/** Short identifier used in URLs: 'books', 'pubs', 'cart', 'canvas', etc. */
	id: string;
	/** Human-readable name: 'rBooks', 'rPubs', 'rCart', etc. */
	name: string;
	/** Emoji or SVG string for the app switcher */
	icon: string;
	/** One-line description */
	description: string;
	/** Mountable Hono sub-app. Routes are relative to the mount point. */
	routes: Hono;

	// ── Scoping & Schema ──

	/** How this module's data is scoped (space vs global) */
	scoping: ModuleScoping;
	/** Automerge document schemas this module manages */
	docSchemas?: DocSchema[];

	// ── Lifecycle hooks ──

	/** Called once at server startup (register schemas, init DB, etc.) */
	onInit?: (ctx: { syncServer: SyncServer }) => Promise<void>;
	/** Called when a new space is created */
	onSpaceCreate?: (ctx: SpaceLifecycleContext) => Promise<void>;
	/** Called when a space is deleted */
	onSpaceDelete?: (ctx: SpaceLifecycleContext) => Promise<void>;
	/** Called when this module is enabled for a space */
	onSpaceEnable?: (ctx: SpaceLifecycleContext) => Promise<void>;
	/** Called when this module is disabled for a space */
	onSpaceDisable?: (ctx: SpaceLifecycleContext) => Promise<void>;

	// ── Display & routing ──

	/** Optional: standalone domain for this module (e.g. 'rbooks.online') */
	standaloneDomain?: string;
	/** Feeds this module exposes to other layers */
	feeds?: FeedDefinition[];
	/** Feed kinds this module can consume from other layers */
	acceptsFeeds?: FlowKind[];
	/** If true, module is hidden from app switcher (still has routes) */
	hidden?: boolean;
	/** Browsable content types this module produces */
	outputPaths?: OutputPath[];
	/** Optional: render rich landing page body HTML */
	landingPage?: () => string;
	/** Info pages for sub-paths on bare domain (replaces demo rewrite with marketing page) */
	subPageInfos?: SubPageInfo[];
	/** Optional: external app to embed via iframe when ?view=app */
	externalApp?: {
		url: string;
		name: string;
	};

	/** Seed template/demo data for a space. Called by /template route. */
	seedTemplate?: (space: string) => void;

	/** Onboarding CTAs shown when the module has no data in a space */
	onboardingActions?: OnboardingAction[];

	/** If true, write operations (POST/PUT/PATCH/DELETE) skip the space role check.
	 *  Use for modules whose API endpoints are publicly accessible (e.g. thread builder). */
	publicWrite?: boolean;

	/** Per-module settings schema for space-level configuration */
	settingsSchema?: ModuleSettingField[];

	/** Applet definitions this module exposes for canvas rApplet cards */
	applets?: import("./applet-types").AppletDefinition[];
	/** Canvas shape tag names this module owns (e.g. ["folk-commitment-pool"]) */
	canvasShapes?: string[];
	/** Canvas AI tool IDs this module owns (e.g. ["create_commitment_pool"]) */
	canvasToolIds?: string[];
}

/** Registry of all loaded modules */
const modules = new Map<string, RSpaceModule>();

export function registerModule(mod: RSpaceModule): void {
	modules.set(mod.id, mod);
}

export function getModule(id: string): RSpaceModule | undefined {
	return modules.get(id);
}

export function getAllModules(): RSpaceModule[] {
	return Array.from(modules.values());
}

/** Metadata exposed to the client for the app switcher and tab bar */
export interface ModuleInfo {
	id: string;
	name: string;
	icon: string;
	description: string;
	scoping: ModuleScoping;
	standaloneDomain?: string;
	feeds?: FeedDefinition[];
	acceptsFeeds?: FlowKind[];
	hidden?: boolean;
	hasLandingPage?: boolean;
	externalApp?: {
		url: string;
		name: string;
	};
	outputPaths?: OutputPath[];
	subPageInfos?: Array<{ path: string; title: string }>;
	settingsSchema?: ModuleSettingField[];
	onboardingActions?: OnboardingAction[];
	canvasShapes?: string[];
	canvasToolIds?: string[];
}

export function getModuleInfoList(): ModuleInfo[] {
	return getAllModules()
		.filter((m) => !m.hidden)
		.map((m) => ({
			id: m.id,
			name: m.name,
			icon: m.icon,
			description: m.description,
			scoping: m.scoping,
			...(m.standaloneDomain ? { standaloneDomain: m.standaloneDomain } : {}),
			...(m.feeds ? { feeds: m.feeds } : {}),
			...(m.acceptsFeeds ? { acceptsFeeds: m.acceptsFeeds } : {}),
			...(m.landingPage ? { hasLandingPage: true } : {}),
			...(m.externalApp ? { externalApp: m.externalApp } : {}),
			...(m.outputPaths ? { outputPaths: m.outputPaths } : {}),
			...(m.subPageInfos ? { subPageInfos: m.subPageInfos.map(s => ({ path: s.path, title: s.title })) } : {}),
			...(m.settingsSchema ? { settingsSchema: m.settingsSchema } : {}),
			...(m.onboardingActions ? { onboardingActions: m.onboardingActions } : {}),
			...(m.canvasShapes ? { canvasShapes: m.canvasShapes } : {}),
			...(m.canvasToolIds ? { canvasToolIds: m.canvasToolIds } : {}),
		}));
}
