/**
 * Standalone UI Configuration Types
 *
 * SDK-independent types for configuring UI templates.
 * These types can be used by external consumers (like AgentLink)
 * without requiring @frontmcp/sdk as a dependency.
 *
 * @packageDocumentation
 */

import type { FileSource } from '../component/types';
import type { CDNDependency } from '../resolver/types';
import type { CustomShellSource } from '../shell/custom-shell-types';

/**
 * Configuration options for bundling file-based components.
 */
export interface FileBundleOptions {
  /** Minify the bundled output */
  minify?: boolean;
  /** Generate source maps for debugging */
  sourceMaps?: boolean;
  /** Target JavaScript version */
  target?: 'es2018' | 'es2019' | 'es2020' | 'es2021' | 'es2022' | 'esnext';
  /** Enable tree shaking */
  treeShake?: boolean;
  /** JSX factory function */
  jsxFactory?: string;
  /** JSX fragment factory */
  jsxFragment?: string;
  /** JSX import source for automatic runtime */
  jsxImportSource?: string;
}

// ============================================
// Content Security Policy
// ============================================

/**
 * Content Security Policy for UI templates rendered in sandboxed iframes.
 * Based on OpenAI Apps SDK and MCP Apps (SEP-1865) specifications.
 *
 * **Where this is emitted (issue #455 fix):** FrontMCP attaches this
 * configuration to the **resource** content item returned by
 * `resources/read` for `ui://widget/{toolName}.html`, as both
 * `_meta.ui.csp` (nested) and `_meta['ui/csp']` (slash) with snake_case
 * `connect_domains` / `resource_domains` fields. MCP Apps hosts (notably
 * Claude) only honor CSP declared on the resource — declarations on the
 * tool's `_meta.ui.csp` are ignored.
 */
export interface UIContentSecurityPolicy {
  /**
   * Origins allowed for fetch/XHR/WebSocket connections.
   * Maps to CSP `connect-src` directive.
   * @example ['https://api.example.com', 'https://*.myservice.com']
   */
  connectDomains?: string[];

  /**
   * Origins allowed for images, scripts, fonts, and styles.
   * Maps to CSP `img-src`, `script-src`, `style-src`, `font-src` directives.
   * @example ['https://cdn.example.com']
   */
  resourceDomains?: string[];
}

/**
 * XSS protection and content security settings.
 *
 * Controls sanitization of HTML content rendered in widgets.
 * By default, strict sanitization is applied to prevent XSS attacks.
 *
 * ## Platform Isolation Context
 *
 * Both OpenAI and Claude render widgets in **double-iframe isolation**:
 *
 * ```
 * ┌─────────────────────────────────────────────────┐
 * │  ChatGPT / Claude Desktop                       │
 * │  ┌─────────────────────────────────────────────┐│
 * │  │  Outer Sandbox Iframe                       ││
 * │  │  - sandbox="allow-scripts allow-same-origin"││
 * │  │  - No access to parent cookies              ││
 * │  │  ┌─────────────────────────────────────────┐││
 * │  │  │  Inner Widget Iframe                    │││
 * │  │  │  - CSP: script-src 'self' 'unsafe-inline'│││
 * │  │  │  - CSP: connect-src based on config     │││
 * │  │  │  - Your widget HTML renders here        │││
 * │  │  └─────────────────────────────────────────┘││
 * │  └─────────────────────────────────────────────┘│
 * └─────────────────────────────────────────────────┘
 * ```
 *
 * This isolation means XSS attacks are **contained** but can still:
 * - Access widget data (input/output)
 * - Make API calls within CSP-allowed domains
 * - Display fake/phishing UI to users
 *
 * **Recommendation:** Only disable protection for fully trusted content.
 */
export interface UIContentSecurity {
  /**
   * Allow `javascript:` and other potentially dangerous URL schemes in links.
   *
   * When `false` (default), URLs are validated to only allow:
   * - `http://`, `https://` (web URLs)
   * - `/`, `#` (relative paths, anchors)
   * - `mailto:` (email links)
   *
   * When `true`, allows any URL scheme including:
   * - `javascript:` (inline script execution)
   * - `data:` (data URIs)
   * - `vbscript:` (legacy script)
   *
   * @default false
   */
  allowUnsafeLinks?: boolean;

  /**
   * Allow inline `<script>` tags and event handlers (onclick, onerror, etc).
   *
   * When `false` (default), HTML content is sanitized to remove:
   * - `<script>...</script>` tags
   * - Event handler attributes (`onclick`, `onerror`, `onload`, etc.)
   *
   * When `true`, these elements are preserved in the output.
   *
   * **Note:** Even with this enabled, CSP may still block script execution
   * depending on the platform's iframe sandbox settings.
   *
   * @default false
   */
  allowInlineScripts?: boolean;

  /**
   * Completely bypass all HTML sanitization.
   *
   * **⚠️ DANGEROUS:** Only use with fully trusted, server-generated content.
   *
   * When `true`, no sanitization is applied:
   * - Script tags are preserved
   * - Event handlers are preserved
   * - All URL schemes are allowed
   * - No HTML escaping is performed
   *
   * This is useful for:
   * - Embedding trusted third-party widgets
   * - Complex interactive dashboards from trusted sources
   * - Content that was pre-sanitized server-side
   *
   * @default false
   */
  bypassSanitization?: boolean;
}

// ============================================
// Template Context & Helpers
// ============================================

/**
 * Helper functions available in template context.
 */
export interface TemplateHelpers {
  /**
   * Escape HTML special characters to prevent XSS.
   * Handles null/undefined by returning empty string.
   * Non-string values are converted to string before escaping.
   */
  escapeHtml: (str: unknown) => string;

  /**
   * Format a date for display.
   * @param date - Date object or ISO string
   * @param format - Optional format (default: localized date)
   */
  formatDate: (date: Date | string, format?: string) => string;

  /**
   * Format a number as currency.
   * @param amount - The numeric amount
   * @param currency - ISO 4217 currency code (default: 'USD')
   */
  formatCurrency: (amount: number, currency?: string) => string;

  /**
   * Generate a unique ID for DOM elements.
   * @param prefix - Optional prefix for the ID
   */
  uniqueId: (prefix?: string) => string;

  /**
   * Safely embed JSON data in HTML (escapes script-breaking characters).
   */
  jsonEmbed: (data: unknown) => string;
}

/**
 * Context passed to template builder functions.
 * Contains all data needed to render a tool's UI template.
 */
export interface TemplateContext<In = unknown, Out = unknown> {
  /**
   * The input arguments passed to the tool.
   */
  input: In;

  /**
   * The raw output returned by the tool's execute method.
   */
  output: Out;

  /**
   * The structured content parsed from the output (if outputSchema was provided).
   * This is the JSON-serializable form suitable for widget consumption.
   */
  structuredContent?: unknown;

  /**
   * Helper functions for template rendering.
   */
  helpers: TemplateHelpers;
}

/**
 * Template builder function type.
 * Receives context with input/output and returns HTML string.
 */
export type TemplateBuilderFn<In = unknown, Out = unknown> = (ctx: TemplateContext<In, Out>) => string;

// ============================================
// Widget Serving Mode
// ============================================

/**
 * Widget serving mode determines how the widget HTML is delivered to the client.
 *
 * - `'auto'` (default): Automatically select mode based on client capabilities.
 *   For OpenAI/ext-apps: uses 'inline'. For Claude: uses 'inline' with dual-payload.
 *   For unsupported clients (e.g., Gemini): skips UI entirely (returns JSON only).
 *
 * - `'inline'`: HTML embedded directly in tool response `_meta['ui/html']`.
 *   Works on all platforms including network-blocked ones, **provided the HTML
 *   itself is fully self-contained** (no external `<script src>` / module
 *   import maps). See the Claude target note on {@link UITemplateConfig.resourceMode}.
 *
 * - `'static'`: Pre-compiled at startup, resolved via `tools/list` (ui:// resource URI).
 *   Widget is fetched via MCP `resources/read`.
 *
 * - `'hybrid'`: Shell (React runtime + bridge) pre-compiled at startup.
 *   Component code transpiled per-request and delivered in `_meta['ui/component']`.
 *
 * - `'direct-url'`: HTTP endpoint on MCP server.
 *
 * - `'custom-url'`: Custom URL (CDN or external hosting).
 *
 * ## Claude target caveat (issue #447)
 *
 * Claude Desktop / claude.ai render widgets in a sandboxed iframe whose CSP
 * blocks **all** external script execution — including esm.sh — regardless of
 * what's set on the tool's `_meta.ui.csp` (Claude only honors CSP declared
 * on the UI resource; see issue #455). React `FileSource` templates and any
 * widget that loads runtime modules from a CDN will silently hang on the
 * "Loading widget…" placeholder in Claude. Only fully self-contained HTML
 * (all JS inline, no import map, no `<script src="https://…">`) renders
 * reliably in Claude today.
 */
export type WidgetServingMode =
  | 'auto' // Automatically select based on client capabilities (default)
  | 'inline' // HTML embedded directly in tool response _meta
  | 'static' // Pre-compiled at startup, resolved via tools/list (ui:// resource URI)
  | 'hybrid' // Shell (React + renderer) cached, component + data in response
  | 'direct-url' // HTTP endpoint on MCP server
  | 'custom-url'; // Custom URL (CDN or external hosting)

// ============================================
// Display Mode
// ============================================

/**
 * Widget display mode preference.
 */
export type WidgetDisplayMode = 'inline' | 'fullscreen' | 'pip';

/**
 * A widget sizing value.
 *
 * - `number` — interpreted as CSS pixels (e.g. `420` → `420px`).
 * - `string` — any valid CSS length (e.g. `'50vh'`, `'24rem'`, `'480px'`).
 */
export type WidgetSizeValue = number | string;

// ============================================
// UI Template Configuration
// ============================================
/**
 * UI template configuration for tools.
 * Enables rendering interactive widgets for tool responses in supported hosts
 * (OpenAI Apps SDK, Claude/MCP-UI, etc.).
 *
 * This is a standalone type that doesn't depend on @frontmcp/sdk.
 * Use this type in external systems (like AgentLink) that consume @frontmcp/ui.
 *
 * @example
 * ```typescript
 * const uiConfig: UITemplateConfig = {
 *   template: (ctx) => `
 *     <div class="p-4">
 *       <h2>${ctx.helpers.escapeHtml(ctx.input.location)}</h2>
 *       <p>${ctx.output.temperature}°F</p>
 *     </div>
 *   `,
 *   csp: { connectDomains: ['https://api.weather.com'] },
 *   widgetAccessible: true,
 *   widgetDescription: 'Displays current weather conditions',
 * };
 * ```
 */
export interface UITemplateConfig<In = unknown, Out = unknown> {
  /**
   * Template for rendering tool UI.
   *
   * Supports multiple formats (auto-detected by renderer):
   * - Template builder function: `(ctx) => string` - receives input/output/helpers, returns HTML
   * - Static HTML/MDX string: `"<div>...</div>"` or `"# Title\n<Card />"`
   * - React component: `MyWidget` - receives props with input/output/helpers
   * - FileSource object: `{ file: './my-widget.tsx' }` - transpiled client-side via esm.sh CDN
   */

  template: TemplateBuilderFn<In, Out> | string | ((props: any) => any) | FileSource;

  /**
   * Content Security Policy for the sandboxed widget.
   * Controls which external resources the widget can access.
   */
  csp?: UIContentSecurityPolicy;

  /**
   * Content security and XSS protection settings.
   *
   * By default, FrontMCP sanitizes HTML content to prevent XSS attacks:
   * - Removes `<script>` tags and event handlers (onclick, onerror, etc.)
   * - Validates URL schemes in links (blocks `javascript:`, `data:`, `vbscript:`)
   *
   * These protections can be disabled for trusted content that requires
   * inline scripts, custom event handlers, or special URL schemes.
   *
   * ## Security Context
   *
   * **OpenAI/Claude run widgets in double-iframe isolation:**
   * - Outer sandbox iframe with restricted permissions
   * - Inner content iframe with CSP headers
   * - No access to parent window or cookies
   * - Network requests restricted by CSP
   *
   * This isolation provides defense-in-depth, making XSS less impactful.
   * However, XSS can still:
   * - Steal widget state/data
   * - Make unauthorized API calls (within CSP limits)
   * - Phish users with fake UI
   *
   * **Best Practice:** Keep XSS protection enabled unless you have a specific
   * need for inline scripts/handlers AND trust your content source completely.
   *
   * @example
   * ```typescript
   * // Default: Full XSS protection
   * ui: { template: MyWidget }
   *
   * // Allow inline scripts for trusted dashboard
   * ui: {
   *   template: TrustedDashboard,
   *   contentSecurity: { allowInlineScripts: true }
   * }
   *
   * // Bypass all sanitization for fully trusted HTML
   * ui: {
   *   template: FullyTrustedContent,
   *   contentSecurity: { bypassSanitization: true }
   * }
   * ```
   */
  contentSecurity?: UIContentSecurity;

  /**
   * Whether the widget can invoke tools via the MCP bridge.
   * When true, the widget gains access to `window.mcpBridge.callTool()`.
   * Exposed in `_meta.ui` for platform discovery.
   *
   * Default: false
   */
  widgetAccessible?: boolean;

  /**
   * Preferred display mode for the widget.
   * - 'inline': Rendered inline in the conversation (default)
   * - 'fullscreen': Request fullscreen display
   * - 'pip': Picture-in-picture mode
   *
   * Note: Host may not support all modes; this is a preference hint.
   */
  displayMode?: WidgetDisplayMode;

  /**
   * Preferred initial height for the widget iframe.
   *
   * Applied as the starting height on the shell document (`html`/`body`/`#root`)
   * and exposed to the host via `_meta['ui/preferredHeight']`. A `number` is
   * treated as CSS pixels; a `string` is any valid CSS length (`'50vh'`,
   * `'24rem'`). When `autoResize` is enabled the runtime grows/shrinks from
   * this baseline to fit content.
   */
  preferredHeight?: WidgetSizeValue;

  /**
   * Minimum height for the widget. Applied as `min-height` CSS and exposed via
   * `_meta['ui/minHeight']`. Auto-resize never reports a height below this.
   */
  minHeight?: WidgetSizeValue;

  /**
   * Maximum height for the widget. Applied as `max-height` CSS and exposed via
   * `_meta['ui/maxHeight']`. Auto-resize never reports a height above this.
   */
  maxHeight?: WidgetSizeValue;

  /**
   * CSS `aspect-ratio` for the widget (e.g. `'16 / 9'` or `1.5`). Applied as
   * `aspect-ratio` CSS on the shell and exposed via `_meta['ui/aspectRatio']`.
   * When set, hosts that honor it size the widget by ratio instead of by
   * measured content height.
   */
  aspectRatio?: string | number;

  /**
   * Whether the widget should auto-report its content height to the host as it
   * changes (via a debounced `ResizeObserver` on `#root`).
   *
   * Only takes effect when the widget configures sizing — i.e. at least one
   * sizing field (`preferredHeight` / `minHeight` / `maxHeight` / `aspectRatio` /
   * `autoResize`) is set, which is what causes the runtime to be injected. A
   * widget with no sizing config does not auto-resize at all. When sizing is
   * configured, auto-resize is on unless you set this to `false`.
   *
   * - Claude / static widgets: the host measures DOM height itself, so this is
   *   a CSS-only no-op.
   * - OpenAI: forwarded through the Apps SDK sizing API.
   * - ext-apps hosts: reported via the `ui/setSize` request.
   *
   * @default true (when the widget configures sizing)
   */
  autoResize?: boolean;

  /**
   * Human-readable description shown to users about what the widget does.
   * Exposed in `_meta.ui` for platform discovery.
   */
  widgetDescription?: string;

  /**
   * Status messages shown during tool invocation.
   * Exposed via `ui/toolInvocation/invoking` and `ui/toolInvocation/invoked` meta keys.
   *
   * @example
   * ```typescript
   * invocationStatus: {
   *   invoking: 'Fetching weather data...',
   *   invoked: 'Weather data loaded'
   * }
   * ```
   */
  invocationStatus?: {
    /** Status text shown while tool is executing */
    invoking?: string;
    /** Status text shown after tool execution completes */
    invoked?: string;
  };

  /**
   * How the widget HTML should be served to the client.
   *
   * - `'auto'` (default): Automatically select mode based on client capabilities.
   *   For OpenAI/ext-apps clients: uses `'inline'` with `_meta`.
   *   For Claude clients: uses `'inline'` with dual-payload format.
   *   For unsupported clients (Gemini, unknown): skips UI (returns JSON only).
   *
   * - `'inline'`: HTML embedded directly in tool response `_meta['ui/html']`.
   *   Best for small widgets, works on all platforms including network-blocked ones.
   *
   * - `'static'`: Widget pre-compiled at server startup, registered as MCP resource with `ui://` URI.
   *   Client fetches via `resources/read`. Good for OpenAI's template system.
   *   Widget is resolved during `tools/list`, not dependent on tool call.
   *
   * - `'hybrid'`: Shell (React runtime + bridge) pre-compiled at startup.
   *   Component code transpiled per-request and delivered in `_meta['ui/component']`.
   *
   * - `'direct-url'`: Served from MCP server's HTTP endpoint.
   *   Avoids third-party cookie issues since widget loads from same domain.
   *
   * - `'custom-url'`: Served from a custom URL (CDN, external hosting).
   *   Requires `customWidgetUrl` to be set.
   *
   * @default 'auto'
   */
  servingMode?: WidgetServingMode;

  /**
   * Custom URL for widget serving when `servingMode: 'custom-url'`.
   * The URL can include `{token}` placeholder which will be replaced with
   * the widget access token.
   *
   * @example
   * ```typescript
   * customWidgetUrl: 'https://cdn.example.com/widgets/weather.html?token={token}'
   * ```
   */
  customWidgetUrl?: string;

  /**
   * Path for direct URL serving when `servingMode: 'direct-url'`.
   * This path is appended to the MCP server's base URL.
   *
   * @example
   * ```typescript
   * directPath: '/widgets/weather'
   * // Results in: https://mcp-server.example.com/widgets/weather?token=...
   * ```
   */
  directPath?: string;

  /**
   * Custom React components available in MDX templates.
   * These components can be used directly in MDX content without importing.
   *
   * @example
   * ```typescript
   * ui: {
   *   template: `# Weather\n<Alert type="info">Data loaded</Alert>`,
   *   mdxComponents: {
   *     Alert: ({ type, children }) => <div className={type}>{children}</div>,
   *     Card: MyCardComponent,
   *   }
   * }
   * ```
   */

  mdxComponents?: Record<string, any>;

  // ============================================
  // MCP Apps (ext-apps) Specific Options
  // ============================================

  /**
   * MCP Apps: Explicit resource URI for the UI template.
   * If not specified, auto-generated as `ui://widget/{toolName}.html`
   *
   * Use this when you need a specific URI pattern for your widget,
   * such as when integrating with existing resource systems.
   *
   * @example 'ui://my-app/dashboard.html'
   * @example 'ui://weather-service/forecast.html'
   */
  resourceUri?: string;

  /**
   * MCP Apps: Widget capabilities for ext-apps initialization.
   *
   * These capabilities are communicated to the host during tool discovery,
   * allowing the host to enable or configure specific features.
   */
  widgetCapabilities?: {
    /** Widget can emit tool list changes (dynamic tool registration) */
    toolListChanged?: boolean;
    /** Widget can handle partial tool input streaming */
    supportsPartialInput?: boolean;
  };

  /**
   * Whether to show a border around the UI widget.
   * MCP Apps spec: `_meta.ui.prefersBorder`
   *
   * When true, hosts should render a visible border around the sandbox iframe.
   * Useful for visual clarity when the widget content doesn't have its own borders.
   *
   * Default: undefined (host decides)
   */
  prefersBorder?: boolean;

  /**
   * Dedicated sandbox domain for the widget.
   * MCP Apps spec: `_meta.ui.domain`
   *
   * When specified, the host should load the widget in an iframe with this
   * domain as the origin, providing additional isolation.
   *
   * @example 'sandbox.example.com'
   */
  sandboxDomain?: string;

  // ============================================
  // Rendering Options
  // ============================================

  /**
   * Enable client-side React hydration after server-side rendering (SSR).
   *
   * **Default: `false`**
   *
   * When `false` (default):
   * - SSR output is static HTML
   * - No React runtime is loaded on the client
   * - All interactivity is handled by the FrontMCP Bridge IIFE
   * - This prevents React Hydration Error #418 in MCP clients
   *
   * When `true`:
   * - React/ReactDOM CDN scripts are included
   * - Client attempts to hydrate React components
   * - May cause hydration mismatch errors in some MCP clients (Claude, ChatGPT)
   *
   * **Why disabled by default?**
   * MCP clients like Claude and ChatGPT render HTML in sandboxed iframes.
   * React hydration expects the client-rendered HTML to exactly match the
   * server-rendered HTML. Due to timing differences, random data, and
   * different execution contexts, mismatches frequently occur causing
   * React Error #418 ("Hydration failed...").
   *
   * The FrontMCP Bridge IIFE already provides all necessary interactivity
   * (tool calls via `data-tool-call`, state updates, loading states) without
   * requiring React hydration.
   *
   * **Only enable if:**
   * - You need React state management on the client
   * - You're sure your template produces deterministic HTML
   * - You've tested hydration works in your target MCP client
   *
   * @example
   * ```typescript
   * // Disable hydration (default behavior - recommended)
   * ui: {
   *   template: MyReactComponent,
   *   hydrate: false
   * }
   *
   * // Enable hydration (use with caution)
   * ui: {
   *   template: MyStatefulComponent,
   *   hydrate: true
   * }
   * ```
   */
  hydrate?: boolean;

  // ============================================
  // Runtime/Build Configuration (from ui-runtime.ts)
  // ============================================

  /**
   * UI renderer type.
   *
   * - `'html'`: HTML with optional Handlebars
   * - `'react'`: React component (SSR)
   * - `'mdx'`: MDX template
   * - `'markdown'`: Pure Markdown
   * - `'auto'`: Auto-detect from template (default)
   *
   * @default 'auto'
   */
  uiType?: 'html' | 'react' | 'mdx' | 'markdown' | 'auto';

  /**
   * Bundling mode.
   *
   * - `'static'`: Pre-compile widget shell, inject data at runtime (default)
   * - `'dynamic'`: Generate fresh HTML per tool invocation
   *
   * @default 'static'
   */
  bundlingMode?: 'static' | 'dynamic';

  /**
   * Resource loading mode for renderer runtime scripts (React, MDX, Handlebars).
   *
   * - `'cdn'`: Load runtime scripts from CDN URLs (lightweight). Works in OpenAI
   *   Apps SDK, ChatGPT, Cursor, MCP Inspector, and other CDN-permissive hosts.
   *
   * - `'inline'`: Embed everything in the HTML. For `FileSource` (`.tsx`/`.jsx`)
   *   templates, React and ReactDOM are bundled into the widget's
   *   `<script type="module">` (no esm.sh import map). The output is a fully
   *   self-contained widget that runs in hosts which block external script
   *   execution — including Claude Desktop / claude.ai (fix shipped for #454).
   *
   * @default `'cdn'` for most platforms. When left unset, FrontMCP host-detects
   *   the right default: `platformType === 'claude'` auto-switches to
   *   `'inline'` so widgets actually render under Claude's CSP (#456). Set
   *   the value explicitly to opt out of the detection.
   *
   *   **Auto-detection only applies to per-call rendering modes (inline /
   *   hybrid / lean).** Static-mode widgets compile at server startup, before
   *   any client connects, so the platform isn't known. If your tool uses
   *   `servingMode: 'static'` and targets Claude, set `resourceMode: 'inline'`
   *   explicitly.
   */
  resourceMode?: 'cdn' | 'inline';

  /**
   * Runtime options for specific renderers.
   */
  runtimeOptions?: {
    /** Enable React hydration after SSR */
    hydrate?: boolean;
    /** Markdown rendering options */
    markdown?: {
      /** Enable GitHub-flavored markdown */
      gfm?: boolean;
      /** Syntax highlighting theme */
      highlightTheme?: string;
    };
  };

  // ============================================
  // Dual-Payload Options (Claude Artifacts)
  // ============================================

  /**
   * Prefix text shown before HTML in dual-payload responses (Claude).
   *
   * When the output mode is `'dual-payload'` (auto-detected for Claude clients),
   * the tool response contains two TextContent blocks:
   * 1. Pure JSON data: `{"temperature": 72, ...}`
   * 2. Markdown-wrapped HTML: `{prefix}:\n\n```html\n<!DOCTYPE html>...\n```
   *
   * This field controls the prefix text in block 2.
   *
   * @default 'Here is the visual result'
   *
   * @example
   * ```typescript
   * // Weather tool
   * ui: {
   *   template: WeatherWidget,
   *   htmlResponsePrefix: 'Here is the weather dashboard',
   * }
   * // Output block 2: "Here is the weather dashboard:\n\n```html\n..."
   *
   * // Stock tool
   * ui: {
   *   template: StockWidget,
   *   htmlResponsePrefix: 'Here is the stock information',
   * }
   * // Output block 2: "Here is the stock information:\n\n```html\n..."
   * ```
   */
  htmlResponsePrefix?: string;

  // ============================================
  // File-Based Template Options (NEW)
  // ============================================

  /**
   * Packages to load from CDN instead of bundling.
   *
   * When `template` is a file path (e.g., `'./chart-widget.tsx'`), imports
   * of these packages will be excluded from the bundle and loaded at runtime
   * via CDN import maps.
   *
   * Package names should match npm package names. The CDN URL is resolved
   * from the built-in CDN registry or from explicit `dependencies` overrides.
   *
   * **Platform considerations (issue #447):**
   * - **Claude Desktop / claude.ai**: the widget iframe blocks all external
   *   script execution regardless of CSP, including `cdnjs.cloudflare.com`.
   *   Even pinning `dependencies` to cdnjs URLs is not enough today —
   *   `.tsx` `FileSource` widgets that rely on a CDN import map for React
   *   itself hang on the "Loading widget…" placeholder (see #454 / #455).
   *   Use a fully self-contained `uiType: 'html'` function template for
   *   Claude until those land.
   * - **OpenAI Apps SDK / ChatGPT / Cursor / MCP Inspector**: any CDN works;
   *   esm.sh is the default. Pinning to `cdnjs.cloudflare.com` via
   *   `dependencies` overrides is recommended only when targeting hosts
   *   with stricter CSP than esm.sh allows.
   *
   * @example
   * ```typescript
   * // Auto-resolved from CDN registry
   * ui: {
   *   template: './chart-widget.tsx',
   *   externals: ['chart.js', 'react-chartjs-2'],
   * }
   *
   * // With explicit override
   * ui: {
   *   template: './chart-widget.tsx',
   *   externals: ['chart.js'],
   *   dependencies: {
   *     'chart.js': {
   *       url: 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js',
   *       integrity: 'sha512-...',
   *       global: 'Chart',
   *     },
   *   },
   * }
   * ```
   */
  externals?: string[];

  /**
   * Explicit CDN dependency overrides for external packages.
   *
   * Use this to specify custom CDN URLs or override the default CDN registry
   * entries for packages listed in `externals`.
   *
   * Keys are npm package names. Values specify the CDN URL, integrity hash,
   * and other loading options.
   *
   * @example
   * ```typescript
   * ui: {
   *   template: './dashboard.tsx',
   *   externals: ['d3', 'lodash'],
   *   dependencies: {
   *     'd3': {
   *       url: 'https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js',
   *       integrity: 'sha512-...',
   *       global: 'd3',
   *     },
   *     'lodash': {
   *       url: 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js',
   *       global: '_',
   *     },
   *   },
   * }
   * ```
   */
  dependencies?: Record<string, CDNDependency>;

  /**
   * Bundle options for file-based templates.
   *
   * Controls how the file-based template is compiled and bundled.
   * These options are only used when `template` is a file path.
   *
   * @example
   * ```typescript
   * // Development mode
   * ui: {
   *   template: './debug-widget.tsx',
   *   bundleOptions: {
   *     minify: false,
   *     sourceMaps: true,
   *     target: 'esnext',
   *   },
   * }
   *
   * // Production mode (defaults)
   * ui: {
   *   template: './widget.tsx',
   *   bundleOptions: {
   *     minify: true,
   *     treeShake: true,
   *     target: 'es2020',
   *   },
   * }
   * ```
   */
  fileBundleOptions?: FileBundleOptions;

  /**
   * Custom HTML shell template with placeholders.
   *
   * Instead of the default HTML document, use a custom template
   * containing `{{CONTENT}}` (required) and optional `{{CSP}}`,
   * `{{DATA}}`, `{{BRIDGE}}`, `{{TITLE}}` placeholders.
   *
   * Source: inline string, URL, or npm package.
   *
   * @example
   * ```typescript
   * // Inline
   * customShell: { inline: '<html><head>{{CSP}}{{DATA}}</head><body>{{CONTENT}}</body></html>' }
   *
   * // URL
   * customShell: { url: 'https://cdn.example.com/my-shell.html' }
   *
   * // NPM
   * customShell: { npm: '@acme/mcp-shell-template' }
   * ```
   */
  customShell?: CustomShellSource;
}

/**
 * Type alias for backwards compatibility.
 * Prefer using UITemplateConfig directly.
 */
export type UITemplate<In = unknown, Out = unknown> = UITemplateConfig<In, Out>;
