/**
 * Runtime utilities for EmDash
 *
 * This file contains functions that are used at runtime (in middleware, routes, etc.)
 * and must work in all environments including Cloudflare Workers.
 *
 * DO NOT import Node.js-only modules here (fs, path, module, etc.)
 */

import type { AuthDescriptor, AuthProviderDescriptor } from "../../auth/types.js";
import type { DatabaseDescriptor } from "../../db/adapters.js";
import type { MediaProviderDescriptor } from "../../media/types.js";
import type { ResolvedPlugin } from "../../plugins/types.js";
import type { StorageDescriptor } from "../storage/types.js";

export type { ResolvedPlugin };
export type { MediaProviderDescriptor };

/**
 * Admin page definition (copied from plugins/types to avoid circular deps)
 */
export interface PluginAdminPage {
	path: string;
	label: string;
	icon?: string;
}

/**
 * Dashboard widget definition (copied from plugins/types to avoid circular deps)
 */
export interface PluginDashboardWidget {
	id: string;
	size?: "full" | "half" | "third";
	title?: string;
}

/**
 * Plugin descriptor - returned by plugin factory functions
 *
 * Contains all static metadata needed for manifest and admin UI,
 * plus the entrypoint for runtime instantiation.
 *
 * @example
 * ```ts
 * export function myPlugin(options?: MyPluginOptions): PluginDescriptor {
 *   return {
 *     id: "my-plugin",
 *     version: "1.0.0",
 *     entrypoint: "@my-org/emdash-plugin-foo",
 *     options: options ?? {},
 *     adminEntry: "@my-org/emdash-plugin-foo/admin",
 *     adminPages: [{ path: "/settings", label: "Settings" }],
 *   };
 * }
 * ```
 */
/**
 * Storage collection declaration for sandboxed plugins
 */
export interface StorageCollectionDeclaration {
	indexes?: string[];
	uniqueIndexes?: string[];
}

export interface PluginDescriptor<TOptions = Record<string, unknown>> {
	/** Unique plugin identifier */
	id: string;
	/** Plugin version (semver) */
	version: string;
	/** Module specifier to import (e.g., "@emdash-cms/plugin-api-test") */
	entrypoint: string;
	/**
	 * Options to pass to createPlugin(). Native format only.
	 * Standard-format plugins configure themselves via KV settings
	 * and Block Kit admin pages -- not constructor options.
	 */
	options?: TOptions;
	/**
	 * Plugin format. Determines how the entrypoint is loaded:
	 * - `"standard"` -- exports `definePlugin({ hooks, routes })` as default.
	 *   Wrapped with `adaptSandboxEntry` for in-process execution. Can run in both
	 *   `plugins: []` (in-process) and `sandboxed: []` (isolate).
	 * - `"native"` -- exports `createPlugin(options)` returning a `ResolvedPlugin`.
	 *   Can only run in `plugins: []`. Cannot be sandboxed or published to marketplace.
	 *
	 * Defaults to `"native"` when unset.
	 *
	 */
	format?: "standard" | "native";
	/** Admin UI module specifier (e.g., "@emdash-cms/plugin-audit-log/admin") */
	adminEntry?: string;
	/** Module specifier for site-side Astro rendering components (must export `blockComponents`) */
	componentsEntry?: string;
	/** Admin pages for navigation */
	adminPages?: PluginAdminPage[];
	/** Dashboard widgets */
	adminWidgets?: PluginDashboardWidget[];

	// === Sandbox-specific fields (for sandboxed plugins) ===

	/**
	 * Capabilities the plugin requests.
	 * For standard-format plugins, capabilities are enforced in both trusted and
	 * sandboxed modes via the PluginContextFactory.
	 */
	capabilities?: string[];
	/**
	 * Allowed hosts for network:fetch capability
	 * Supports wildcards like "*.example.com"
	 */
	allowedHosts?: string[];
	/**
	 * Storage collections the plugin declares
	 * Sandboxed plugins can only access declared collections.
	 */
	storage?: Record<string, StorageCollectionDeclaration>;
}

/**
 * Sandboxed plugin descriptor - same format as PluginDescriptor
 *
 * These run in isolated V8 isolates via Worker Loader on Cloudflare.
 * The `entrypoint` is resolved to a file and bundled at build time.
 */
export type SandboxedPluginDescriptor<TOptions = Record<string, unknown>> =
	PluginDescriptor<TOptions>;

export interface EmDashConfig {
	/**
	 * Database configuration
	 *
	 * Use one of the adapter functions:
	 * - `sqlite({ url: "file:./data.db" })` - Local SQLite
	 * - `libsql({ url: "...", authToken: "..." })` - Turso/libSQL
	 * - `d1({ binding: "DB" })` - Cloudflare D1
	 *
	 * @example
	 * ```ts
	 * import { sqlite } from "emdash/db";
	 *
	 * emdash({
	 *   database: sqlite({ url: "file:./data.db" }),
	 * })
	 * ```
	 */
	database?: DatabaseDescriptor;
	/**
	 * Storage configuration (for media)
	 */
	storage?: StorageDescriptor;
	/**
	 * Trusted plugins to load (run in main isolate)
	 *
	 * @example
	 * ```ts
	 * import { auditLogPlugin } from "@emdash-cms/plugin-audit-log";
	 * import { webhookNotifierPlugin } from "@emdash-cms/plugin-webhook-notifier";
	 *
	 * emdash({
	 *   plugins: [
	 *     auditLogPlugin(),
	 *     webhookNotifierPlugin({ url: "https://example.com/webhook" }),
	 *   ],
	 * })
	 * ```
	 */
	plugins?: PluginDescriptor[];
	/**
	 * Sandboxed plugins to load (run in isolated V8 isolates)
	 *
	 * Only works on Cloudflare with Worker Loader enabled.
	 * Uses the same format as `plugins` - the difference is where they run.
	 *
	 * @example
	 * ```ts
	 * import { untrustedPlugin } from "some-third-party-plugin";
	 *
	 * emdash({
	 *   plugins: [trustedPlugin()],     // runs in host
	 *   sandboxed: [untrustedPlugin()], // runs in isolate
	 *   sandboxRunner: "@emdash-cms/sandbox-cloudflare",
	 * })
	 * ```
	 */
	sandboxed?: SandboxedPluginDescriptor[];
	/**
	 * Module that exports the sandbox runner factory.
	 * Required if using sandboxed plugins.
	 *
	 * @example
	 * ```ts
	 * emdash({
	 *   sandboxRunner: "@emdash-cms/sandbox-cloudflare",
	 * })
	 * ```
	 */
	sandboxRunner?: string;

	/**
	 * Authentication configuration
	 *
	 * Use an auth adapter function from a platform package:
	 * - `access({ teamDomain: "..." })` from `@emdash-cms/cloudflare`
	 *
	 * When an external auth provider is configured, passkey auth is disabled.
	 *
	 * @example
	 * ```ts
	 * import { access } from "@emdash-cms/cloudflare";
	 *
	 * emdash({
	 *   auth: access({
	 *     teamDomain: "myteam.cloudflareaccess.com",
	 *     audience: "abc123...",
	 *     roleMapping: {
	 *       "Admins": 50,
	 *       "Editors": 30,
	 *     },
	 *   }),
	 * })
	 * ```
	 */
	auth?: AuthDescriptor;

	/**
	 * Pluggable auth providers (login methods on the login page).
	 *
	 * Auth providers appear as options alongside passkey on the login page
	 * and setup wizard. Any provider can be used to create the initial
	 * admin account. Passkey is built-in; providers listed here are additive.
	 *
	 * @example
	 * ```ts
	 * import { atproto } from "@emdash-cms/auth-atproto";
	 *
	 * emdash({
	 *   authProviders: [atproto()],
	 * })
	 * ```
	 */
	authProviders?: AuthProviderDescriptor[];

	/**
	 * MCP (Model Context Protocol) server endpoint.
	 *
	 * Exposes an MCP Streamable HTTP server at `/_emdash/api/mcp`
	 * that allows AI agents and tools to interact with the CMS using
	 * the standardized MCP protocol.
	 *
	 * Enabled by default. The endpoint requires bearer token auth, so
	 * it has no effect unless the user creates an API token and
	 * configures a client. Set to `false` to disable.
	 *
	 * @default true
	 */
	mcp?: boolean;

	/**
	 * Plugin marketplace URL
	 *
	 * When set, enables the marketplace features: browse, install, update,
	 * and uninstall plugins from a remote marketplace.
	 *
	 * Must be an HTTPS URL in production, or localhost/127.0.0.1 in dev.
	 * Requires `sandboxRunner` to be configured (marketplace plugins run sandboxed).
	 *
	 * @example
	 * ```ts
	 * emdash({
	 *   marketplace: "https://marketplace.emdashcms.com",
	 *   sandboxRunner: "@emdash-cms/sandbox-cloudflare",
	 * })
	 * ```
	 */
	marketplace?: string;

	/**
	 * Maximum allowed media file upload size in bytes.
	 *
	 * Applies to both direct multipart uploads and signed-URL uploads.
	 * When unset, defaults to 52_428_800 (50 MB).
	 *
	 * @example
	 * ```ts
	 * emdash({ maxUploadSize: 100 * 1024 * 1024 }) // 100 MB
	 * ```
	 */
	maxUploadSize?: number;

	/**
	 * Public browser-facing origin for the site.
	 *
	 * Use when `Astro.url` / `request.url` do not match what users open — common with a
	 * **TLS-terminating reverse proxy**: the app often sees `http://` on the internal hop
	 * while the browser uses `https://`, which breaks WebAuthn, CSRF, OAuth, and redirect URLs.
	 *
	 * Set to the full origin users type in the address bar (no path), e.g.
	 * `https://mysite.example.com`. When not set, falls back to environment variables
	 * `EMDASH_SITE_URL` > `SITE_URL`, then to the request URL's origin.
	 *
	 * Replaces `passkeyPublicOrigin` (which only fixed passkeys).
	 */
	siteUrl?: string;

	/**
	 * Additional origins accepted by passkey verification.
	 *
	 * When the same EmDash deployment is reachable under several hostnames sharing
	 * a registrable parent (e.g. `https://example.com` plus
	 * `https://preview.example.com`), the canonical `siteUrl` defines the `rpId`
	 * and the entries here are the *additional* origins from which assertions
	 * are accepted. Each entry must be the same hostname as `siteUrl` or a
	 * subdomain of it — WebAuthn requires `rpId` to be a registrable suffix of
	 * every origin.
	 *
	 * Merged at runtime with the `EMDASH_ALLOWED_ORIGINS` env var (comma-separated).
	 * Validation:
	 *   - Config-declared entries are shape-checked at Astro startup.
	 *   - Subdomain relationship to `siteUrl` is checked at startup when
	 *     `siteUrl` is also config-declared, otherwise at first passkey
	 *     verification (since `siteUrl` may come from `EMDASH_SITE_URL`).
	 *
	 * Mismatches throw with a source-attributed message naming
	 * `config.allowedOrigins` or `EMDASH_ALLOWED_ORIGINS`.
	 *
	 * @example
	 * ```ts
	 * emdash({
	 *   siteUrl: "https://example.com",
	 *   allowedOrigins: ["https://preview.example.com"],
	 * })
	 * ```
	 */
	allowedOrigins?: string[];
	/*
	 * Headers to trust for client IP resolution when running behind a reverse
	 * proxy. The first header in this list that is present on the request
	 * wins. Applies to rate limiting for auth endpoints and comment
	 * submission.
	 *
	 * Common values:
	 * - `x-real-ip` — nginx, Caddy, Traefik
	 * - `fly-client-ip` — Fly.io
	 * - `x-forwarded-for` — generic (first entry is used)
	 *
	 * Only set this when you **control the reverse proxy**. Untrusted
	 * clients can set any header they like; trusting headers from an open
	 * network is an IP-spoofing vulnerability that defeats rate limiting.
	 *
	 * On Cloudflare the `cf` object on the request is used automatically —
	 * you normally don't need to set this. Leave unset (or empty) to
	 * preserve the default: IP is resolved only when the request came
	 * through Cloudflare's edge.
	 *
	 * Falls back to `EMDASH_TRUSTED_PROXY_HEADERS` env var (comma-separated)
	 * when this option is not set, so operators can configure at deploy
	 * time without touching the Astro config.
	 */
	trustedProxyHeaders?: string[];

	/**
	 * Enable playground mode for ephemeral "try EmDash" sites.
	 *
	 * When set, the integration injects a playground middleware (order: "pre")
	 * that runs BEFORE the normal EmDash middleware chain. It creates an
	 * isolated Durable Object database per session, runs migrations, applies
	 * the seed, creates an anonymous admin user, and sets the DB in ALS.
	 * By the time the runtime middleware runs, the database is fully ready.
	 *
	 * Setup and auth middleware are skipped (the playground handles both).
	 *
	 * Requires `@emdash-cms/cloudflare` as a dependency and a DO binding
	 * in wrangler.jsonc.
	 *
	 * @example
	 * ```ts
	 * emdash({
	 *   database: playgroundDatabase({ binding: "PLAYGROUND_DB" }),
	 *   playground: {
	 *     middlewareEntrypoint: "@emdash-cms/cloudflare/db/playground-middleware",
	 *   },
	 * })
	 * ```
	 */
	playground?: {
		/** Module path for the playground middleware. */
		middlewareEntrypoint: string;
	};

	/**
	 * Media providers for browsing and uploading media
	 *
	 * The local media provider (using storage adapter) is available by default.
	 * Additional providers can be added for external services like Unsplash,
	 * Cloudinary, Mux, Cloudflare Images, etc.
	 *
	 * @example
	 * ```ts
	 * import { cloudflareImages, cloudflareStream } from "@emdash-cms/cloudflare";
	 * import { unsplash } from "@emdash-cms/provider-unsplash";
	 *
	 * emdash({
	 *   mediaProviders: [
	 *     cloudflareImages({ accountId: "..." }),
	 *     cloudflareStream({ accountId: "..." }),
	 *     unsplash({ accessKey: "..." }),
	 *   ],
	 * })
	 * ```
	 */
	mediaProviders?: MediaProviderDescriptor[];

	/**
	 * Admin UI font configuration.
	 *
	 * By default, EmDash loads Noto Sans via the Astro Font API, covering
	 * Latin, Latin Extended, Cyrillic, Cyrillic Extended, Greek, Greek
	 * Extended, Devanagari, and Vietnamese. Fonts are downloaded from
	 * Google at build time and self-hosted, so there are no runtime CDN
	 * requests.
	 *
	 * To add support for additional writing systems (Arabic, CJK, etc.),
	 * pass script names. EmDash resolves the matching Noto Sans variant
	 * from Google Fonts and merges all script faces under a single
	 * font-family, so the browser downloads only the glyphs it needs
	 * via unicode-range.
	 *
	 * Set to `false` to disable font injection entirely and use system fonts.
	 *
	 * @example
	 * ```ts
	 * // Add Arabic and Japanese support
	 * emdash({
	 *   fonts: {
	 *     scripts: ["arabic", "japanese"],
	 *   },
	 * })
	 * ```
	 *
	 * @example
	 * ```ts
	 * // Disable web fonts entirely (use system fonts)
	 * emdash({
	 *   fonts: false,
	 * })
	 * ```
	 */
	fonts?:
		| false
		| {
				/**
				 * Additional Noto Sans script families to include.
				 *
				 * Available scripts: arabic, armenian, bengali, chinese-simplified,
				 * chinese-traditional, chinese-hongkong, devanagari, ethiopic, farsi,
				 * georgian, gujarati, gurmukhi, hebrew, japanese, kannada, khmer,
				 * korean, lao, malayalam, myanmar, oriya, sinhala, tamil, telugu,
				 * thai, tibetan.
				 */
				scripts?: string[];
		  };

	/**
	 * Admin UI branding (white-labeling).
	 *
	 * Overrides the default EmDash logo and name in the admin panel.
	 * Use this to white-label the CMS for agency or enterprise deployments.
	 * These settings are separate from the public site settings (title, logo,
	 * favicon) which remain available for SEO and front-end use.
	 *
	 * @example
	 * ```ts
	 * emdash({
	 *   admin: {
	 *     logo: "/images/agency-logo.webp",
	 *     siteName: "AgencyX CMS",
	 *     favicon: "/favicon.ico",
	 *   },
	 * })
	 * ```
	 */
	admin?: {
		/** URL or path to a custom logo image for the admin UI (login page, sidebar). */
		logo?: string;
		/** Custom name displayed in the admin sidebar and browser tab. */
		siteName?: string;
		/** URL or path to a custom favicon for the admin panel. */
		favicon?: string;
	};
}

/**
 * Get stored config from global
 * This is set by the virtual module at build time
 */
export function getStoredConfig(): EmDashConfig | null {
	return globalThis.__emdashConfig || null;
}

/**
 * Set stored config in global
 * Called by the integration at config time
 */
export function setStoredConfig(config: EmDashConfig): void {
	globalThis.__emdashConfig = config;
}

// Declare global type
declare global {
	// eslint-disable-next-line no-var
	var __emdashConfig: EmDashConfig | undefined;
}
