import { guardConfigSchema, type GuardConfig } from '@frontmcp/guard';
import { z } from '@frontmcp/lazy-zod';

import type { AppType, PluginType, ProviderType, ResourceType, SkillType, ToolType } from '../interfaces';
import {
  annotatedFrontMcpAppSchema,
  annotatedFrontMcpPluginsSchema,
  annotatedFrontMcpProvidersSchema,
  annotatedFrontMcpResourcesSchema,
  annotatedFrontMcpSkillsSchema,
  annotatedFrontMcpToolsSchema,
} from '../schemas';
import {
  authOptionsSchema,
  elicitationOptionsSchema,
  extAppsOptionsSchema,
  healthOptionsSchema,
  httpOptionsSchema,
  loggingOptionsSchema,
  metricsOptionsSchema,
  observabilityOptionsSchema,
  paginationOptionsSchema,
  pubsubOptionsSchema,
  redisOptionsSchema,
  serverInfoOptionsSchema,
  skillsConfigOptionsSchema,
  sqliteOptionsSchema,
  transportOptionsSchema,
  type AuthOptions,
  type AuthOptionsInput,
  type ElicitationOptionsInput,
  type ExtAppsOptionsInput,
  type HealthOptionsInput,
  type HttpOptionsInput,
  type LoggingOptionsInput,
  type MetricsOptionsInput,
  type ObservabilityOptionsInterface,
  type PaginationOptions,
  type PubsubOptionsInput,
  type RawZodShape,
  type RedisOptionsInput,
  type ServerInfoOptions,
  type SkillsConfigOptionsInput,
  type SqliteOptionsInput,
  type TransportOptionsInput,
} from '../types';
import { packageLoaderSchema, type PackageLoader } from './app.metadata';
import { channelsConfigSchema, type ChannelsConfigInput } from './channel.metadata';
import { outputPolicySchema, type OutputPolicy } from './output-policy';

export interface FrontMcpBaseMetadata {
  info: ServerInfoOptions;
  apps: AppType[];
  http?: HttpOptionsInput;
  logging?: LoggingOptionsInput;

  serve?: boolean; // default to true

  /**
   * Directory of the source file that declared this `@FrontMcp` class, captured
   * automatically at decoration time (no `fileURLToPath` needed). Anchors
   * relative file paths in the server config — notably `auth.ui[slot]` for the
   * multi-app scope. `undefined` when the call-site couldn't be determined (the
   * registry then falls back to `process.cwd()`). Internal; do not set manually.
   *
   * @internal
   */
  __sourceDir?: string;

  /**
   * Server-level instructions surfaced through the MCP `initialize` response.
   *
   * The MCP `InitializeResult.instructions` field lets the server hand the
   * client a "system prompt" describing how to use available capabilities.
   * FrontMCP merges this string with channel hints and (optionally) bundle
   * skill instructions per `skillsConfig.injectInstructions`.
   *
   * Keep this short — clients typically include it verbatim in the model
   * context window. The full skill catalog is exposed via the
   * `skills://catalog` resource and the `skills://{name}/SKILL.md` resource
   * template (or the `skills/search` MCP extension method).
   *
   * @see `skillsConfig.injectInstructions` for the merge policy. Note that
   *   `'replace'` falls back to `'append'` semantics when `instructions` is
   *   empty/undefined so a misconfigured server doesn't silently drop the
   *   catalog and channel hints.
   */
  instructions?: string;

  /**
   * Shared storage configuration
   * Used by transport persistence and auth token storage.
   * Supports both Redis and Vercel KV providers.
   */
  redis?: RedisOptionsInput;

  /**
   * Pub/Sub configuration (Redis-only)
   * Required for resource subscriptions when using Vercel KV for sessions.
   * Falls back to `redis` config if not specified and redis is configured with Redis provider.
   */
  pubsub?: PubsubOptionsInput;

  /**
   * Transport and session lifecycle configuration
   * Controls transport protocols, session management, and persistence
   * @default {} (all transport options use their schema defaults)
   */
  transport?: TransportOptionsInput; // Optional in input, but always defined in output

  /**
   * Additional providers that are available to all apps.
   */
  providers?: ProviderType[];

  /**
   * Shared tools that are available to all apps.
   * These are merged (additively) with app-specific tools.
   */
  tools?: ToolType[];

  /**
   * Shared resources that are available to all apps.
   * These are merged (additively) with app-specific resources.
   */
  resources?: ResourceType[];

  /**
   * Shared skills that are available to all apps.
   * These are merged (additively) with app-specific skills.
   */
  skills?: SkillType[];

  /**
   * Server-level plugins that are instantiated per scope.
   * Each scope gets its own instance of these plugins.
   * These plugins have server-wide access (can see all apps in scope).
   */
  plugins?: PluginType[];

  /**
   * Pagination configuration for list operations.
   * Currently only tool list pagination is supported (tools/list endpoint).
   */
  pagination?: PaginationOptions;

  /**
   * Elicitation configuration.
   * Controls whether tools can request interactive user input during execution.
   * When enabled, tool output schemas are extended to include elicitation fallback response type.
   * @default { enabled: false }
   */
  elicitation?: ElicitationOptionsInput;

  /**
   * Skills HTTP endpoints configuration.
   * Controls exposure of skills via HTTP endpoints for multi-agent architectures.
   *
   * When enabled, provides:
   * - GET /llm.txt - Compact skill summaries
   * - GET /llm_full.txt - Full skills with instructions and tool schemas
   * - GET /skills - JSON API for listing/searching skills
   * - GET /skills/{id} - Load specific skill by ID
   *
   * @default { enabled: false }
   *
   * @example Enable skills HTTP endpoints
   * ```typescript
   * skillsConfig: { enabled: true }
   * ```
   *
   * @example HTTP-only (disable MCP resources)
   * ```typescript
   * skillsConfig: {
   *   enabled: true,
   *   mcpResources: false,  // No skill:// MCP resources
   * }
   * ```
   */
  skillsConfig?: SkillsConfigOptionsInput;

  /**
   * MCP Apps (ext-apps) configuration.
   * Controls handling of ext-apps widget-to-host communication over HTTP transport.
   *
   * When enabled, the HTTP transport will route `ui/*` JSON-RPC methods
   * (ui/initialize, ui/callServerTool, etc.) through session validation
   * and the ExtAppsMessageHandler.
   *
   * ## Host Capabilities
   *
   * Host capabilities advertise what features the server supports to widgets:
   *
   * | Capability           | Description                                      | Default |
   * |---------------------|--------------------------------------------------|---------|
   * | `serverToolProxy`   | Allow widgets to call MCP tools via ui/callServerTool | `true`  |
   * | `logging`           | Allow widgets to send logs via ui/log            | `true`  |
   * | `openLink`          | Allow widgets to request URL opening via ui/openLink | `false` |
   * | `modelContextUpdate`| Allow widgets to update model context via ui/updateModelContext | `false` |
   * | `widgetTools`       | Allow widgets to register/unregister tools dynamically | `false` |
   * | `displayModes`      | Supported display modes: 'inline', 'fullscreen', 'pip' | `undefined` |
   *
   * @default { enabled: true, hostCapabilities: { serverToolProxy: true, logging: true } }
   *
   * @example Enable ext-apps with default capabilities
   * ```typescript
   * extApps: { enabled: true }
   * ```
   *
   * @example Configure all host capabilities
   * ```typescript
   * extApps: {
   *   enabled: true,
   *   hostCapabilities: {
   *     serverToolProxy: true,    // Widgets can call MCP tools
   *     logging: true,            // Widgets can send logs
   *     openLink: true,           // Widgets can request URL opening
   *     modelContextUpdate: true, // Widgets can update model context
   *     widgetTools: true,        // Widgets can register dynamic tools
   *     displayModes: ['inline', 'fullscreen', 'pip'],
   *   },
   * }
   * ```
   *
   * @example Disable ext-apps
   * ```typescript
   * extApps: { enabled: false }
   * ```
   */
  extApps?: ExtAppsOptionsInput;

  /**
   * SQLite storage configuration for local-only deployments.
   * Provides session, elicitation, and event persistence without Redis.
   * Used by the Unix socket daemon mode and other local runtimes.
   */
  sqlite?: SqliteOptionsInput;

  /**
   * Default package loader config for all npm/esm apps.
   * Individual apps can override this via `packageConfig.loader`.
   */
  loader?: PackageLoader;

  /**
   * UI rendering configuration.
   * Controls CDN overrides for widget import resolution.
   *
   * @example Override @frontmcp/ui to load from local dev server
   * ```typescript
   * ui: {
   *   cdnOverrides: {
   *     '@frontmcp/ui': 'http://localhost:5173/@frontmcp/ui',
   *   },
   * }
   * ```
   */
  ui?: {
    cdnOverrides?: Record<string, string>;
  };

  /**
   * Output policy applied to every tool (overridable per `@App` / `@Tool`):
   *
   * - `allowNonFinite`: lets `Infinity` / `-Infinity` / `NaN` reach JSON serialization
   *   (where they become `null`). Default `false` — non-finite output throws
   *   `InvalidOutputError`, surfacing silent `JSON.stringify` corruption.
   * - `schemaMode`: how `outputSchema` is exposed in `tools/list` — `'definition'`
   *   (default) / `'description'` / `'both'` / `'none'`.
   *
   * @default { allowNonFinite: false, schemaMode: 'definition' }
   */
  output?: OutputPolicy;

  /**
   * Jobs and workflows configuration.
   * Enables the jobs/workflows system for saved, discoverable, triggerable executions.
   *
   * @default { enabled: false }
   *
   * @example Enable jobs system
   * ```typescript
   * jobs: { enabled: true }
   * ```
   *
   * @example With Redis store
   * ```typescript
   * jobs: {
   *   enabled: true,
   *   store: { redis: { provider: 'redis', host: 'localhost' } }
   * }
   * ```
   */
  jobs?: {
    enabled: boolean;
    store?: {
      redis?: RedisOptionsInput;
      keyPrefix?: string;
    };
  };

  /**
   * Rate limiting, concurrency control, and timeout configuration.
   * Controls global and default throttle behavior for all entities.
   *
   * @default { enabled: false }
   *
   * @example Global rate limiting by IP
   * ```typescript
   * throttle: {
   *   enabled: true,
   *   global: { maxRequests: 1000, windowMs: 60_000, partitionBy: 'ip' },
   *   defaultTimeout: { executeMs: 30_000 },
   * }
   * ```
   *
   * @example With Redis backend and per-tool defaults
   * ```typescript
   * throttle: {
   *   enabled: true,
   *   defaultRateLimit: { maxRequests: 60, windowMs: 60_000, partitionBy: 'session' },
   *   defaultConcurrency: { maxConcurrent: 10 },
   *   defaultTimeout: { executeMs: 30_000 },
   * }
   * ```
   */
  throttle?: GuardConfig;

  /**
   * Health and readiness endpoint configuration.
   * Provides Kubernetes-style `/healthz` (liveness) and `/readyz` (readiness) probes
   * with runtime introspection, dependency health probing, and catalog hashing.
   *
   * **Runtime matrix:**
   * - Node/Bun/Deno/Browser: `/healthz` + `/readyz`
   * - Edge/Cloudflare/Vercel: `/healthz` only
   * - CLI: `/healthz` only
   *
   * @default { enabled: true }
   *
   * @example Default (auto-enabled)
   * ```typescript
   * // health endpoints enabled by default
   * ```
   *
   * @example Custom probes
   * ```typescript
   * health: {
   *   probes: [{
   *     name: 'postgres',
   *     async check() {
   *       await pool.query('SELECT 1');
   *       return { status: 'healthy' };
   *     },
   *   }],
   * }
   * ```
   */
  health?: HealthOptionsInput;

  /**
   * `/metrics` endpoint configuration (issue #397).
   *
   * OFF by default. When `enabled: true`, registers a `GET /metrics`
   * endpoint on the same HTTP listener as `/healthz` that emits Prometheus
   * text exposition (or JSON, via `format: 'json'`) covering:
   *   - process metrics — CPU, RSS, heap, event-loop lag, fd count
   *   - every `@frontmcp/observability` counter (`frontmcp_*_total`)
   *
   * Re-uses the in-memory counter snapshot store from
   * `@frontmcp/observability`, so counters emitted via `createCounter()`
   * are scraped without needing an OTel push pipeline.
   *
   * @example Enable with defaults
   * ```typescript
   * metrics: { enabled: true }
   * ```
   *
   * @example Token-gated for internet-exposed deployments
   * ```typescript
   * metrics: {
   *   enabled: true,
   *   auth: 'token',
   *   tokenEnv: 'FRONTMCP_METRICS_TOKEN',
   * }
   * ```
   */
  metrics?: MetricsOptionsInput;

  /**
   * Observability configuration — OpenTelemetry tracing, structured logging,
   * and per-request log collection.
   *
   * When set, the SDK auto-loads `@frontmcp/observability` and registers
   * instrumentation hooks for all flows. No explicit plugin import needed.
   *
   * - `true` — enable tracing with defaults
   * - Object — fine-grained control
   *
   * Requires `@frontmcp/observability` to be installed as a dependency.
   *
   * @example
   * ```typescript
   * observability: {
   *   tracing: true,
   *   logging: { sinks: [{ type: 'stdout' }] },
   *   requestLogs: true,
   * }
   * ```
   */
  observability?: ObservabilityOptionsInterface | boolean;

  /**
   * Channels configuration for Claude Code real-time notifications.
   * Enables push-based event channels using the `notifications/claude/channel` extension.
   *
   * When enabled, the server advertises `experimental: { 'claude/channel': {} }` capability.
   * Channels can be declared via `@Channel()` decorator or `channel()` function builder
   * in app definitions.
   *
   * Supported sources: webhook, app-event, agent-completion, job-completion, manual.
   *
   * @default { enabled: false }
   *
   * @example Enable channels
   * ```typescript
   * channels: { enabled: true }
   * ```
   *
   * @example With default metadata for all notifications
   * ```typescript
   * channels: {
   *   enabled: true,
   *   defaultMeta: { server: 'my-app', team: 'platform' },
   * }
   * ```
   */
  channels?: ChannelsConfigInput;

  /**
   * Authorities configuration for built-in RBAC/ABAC/ReBAC enforcement.
   * Enables the `checkEntryAuthorities` and `filterByAuthorities` flow stages
   * that enforce `authorities` metadata on tools, resources, and prompts.
   *
   * @example Simple role-based setup
   * ```typescript
   * authorities: {
   *   claimsMapping: { roles: 'roles', permissions: 'permissions' },
   *   profiles: {
   *     admin: { roles: { any: ['admin', 'superadmin'] } },
   *   },
   * }
   * ```
   *
   * @example With JWT claims mapping for Keycloak
   * ```typescript
   * authorities: {
   *   claimsMapping: { roles: 'realm_access.roles', permissions: 'scope' },
   *   profiles: {
   *     admin: { roles: { any: ['admin'] } },
   *     matchTenant: {
   *       attributes: {
   *         conditions: [{ path: 'claims.org_id', op: 'eq', value: { fromInput: 'tenantId' } }],
   *       },
   *     },
   *   },
   * }
   * ```
   */
  authorities?: import('@frontmcp/auth').AuthoritiesConfig;

  /**
   * Background tasks configuration per MCP 2025-11-25 tasks spec.
   *
   * Enabled automatically when any tool declares `execution.taskSupport` other
   * than `'forbidden'`. Explicitly set `enabled: false` to disable, or provide
   * custom defaults / a Redis-backed store for multi-node deployments.
   *
   * @example Default (memory, single-node)
   * ```typescript
   * tasks: { enabled: true }
   * ```
   *
   * @example Distributed with Redis
   * ```typescript
   * tasks: {
   *   enabled: true,
   *   redis: { provider: 'redis', host: 'localhost' },
   *   defaultTtlMs: 300_000,
   *   maxTtlMs: 3_600_000,
   * }
   * ```
   */
  tasks?: {
    enabled?: boolean;
    redis?: RedisOptionsInput;
    keyPrefix?: string;
    defaultTtlMs?: number;
    maxTtlMs?: number;
    defaultPollIntervalMs?: number;
    maxConcurrentPerSession?: number;
    /**
     * Throw at startup instead of warning when the runtime cannot run tasks
     * reliably (edge/serverless). Default: `false`.
     */
    strict?: boolean;
    /** Runner selection — see `TasksConfig.runner`. */
    runner?: 'in-process' | 'cli';
    /**
     * Back the task store with a SQLite database (single-file, local, no Redis
     * needed). Required for cross-invocation persistence in CLI mode.
     */
    sqlite?: {
      path: string;
      encryption?: { secret: string };
      walMode?: boolean;
      ttlCleanupIntervalMs?: number;
    };
    /**
     * Command used by the CLI task runner to spawn detached worker processes.
     * When unset, the runner re-invokes the currently-running executable.
     */
    cliRunnerCommand?: {
      exe: string;
      args?: string[];
    };
  };
}

export const frontMcpBaseSchema = z.object({
  info: serverInfoOptionsSchema,
  // Captured call-site dir (#469 — anchors auth.ui relative paths). Passthrough
  // so it survives the strict-object parse and reaches the scope metadata.
  __sourceDir: z.string().optional(),
  instructions: z.string().optional(),
  providers: z.array(annotatedFrontMcpProvidersSchema).optional().default([]),
  tools: z.array(annotatedFrontMcpToolsSchema).optional().default([]),
  resources: z.array(annotatedFrontMcpResourcesSchema).optional().default([]),
  skills: z.array(annotatedFrontMcpSkillsSchema).optional().default([]),
  plugins: z.array(annotatedFrontMcpPluginsSchema).optional().default([]),
  apps: z.array(annotatedFrontMcpAppSchema),
  serve: z.boolean().optional().default(true),
  http: httpOptionsSchema.optional(),
  redis: redisOptionsSchema.optional(),
  pubsub: pubsubOptionsSchema.optional(),
  transport: transportOptionsSchema.optional().transform((val) => val ?? transportOptionsSchema.parse({})),
  logging: loggingOptionsSchema.optional(),
  pagination: paginationOptionsSchema.optional(),
  elicitation: elicitationOptionsSchema.optional(),
  skillsConfig: skillsConfigOptionsSchema.optional(),
  extApps: extAppsOptionsSchema.optional(),
  sqlite: sqliteOptionsSchema.optional(),
  ui: z
    .object({
      cdnOverrides: z.record(z.string(), z.string()).optional(),
    })
    .optional(),
  /**
   * Output-validation policy applied after entry-level Zod parsing succeeds.
   * `allowNonFinite: true` lets `Infinity`, `-Infinity`, and `NaN` reach JSON
   * serialization (where they become `null`). Default is `false` — non-finite
   * numeric output throws `InvalidOutputError`.
   */
  output: outputPolicySchema.optional(),
  jobs: z
    .object({
      enabled: z.boolean(),
      store: z
        .object({
          redis: redisOptionsSchema.optional(),
          keyPrefix: z.string().optional(),
        })
        .optional(),
    })
    .optional(),
  loader: packageLoaderSchema.optional(),
  throttle: guardConfigSchema.optional(),
  observability: observabilityOptionsSchema,
  health: healthOptionsSchema.optional(),
  metrics: metricsOptionsSchema.optional(),
  channels: channelsConfigSchema.optional(),
  authorities: z
    .object({
      claimsMapping: z.record(z.string(), z.string().optional()).optional(),
      profiles: z.record(z.string(), z.looseObject({})).optional(),
      relationshipResolver: z.any().optional(),
      evaluators: z.record(z.string(), z.any()).optional(),
      claimsResolver: z.any().optional(),
      scopeMapping: z
        .object({
          roles: z.record(z.string(), z.array(z.string())).optional(),
          permissions: z.record(z.string(), z.array(z.string())).optional(),
          profiles: z.record(z.string(), z.array(z.string())).optional(),
        })
        .optional(),
      pipes: z.array(z.any()).optional(),
    })
    .optional(),
  tasks: z
    .object({
      enabled: z.boolean().optional(),
      redis: redisOptionsSchema.optional(),
      keyPrefix: z.string().optional(),
      defaultTtlMs: z.number().int().positive().optional(),
      maxTtlMs: z.number().int().positive().optional(),
      defaultPollIntervalMs: z.number().int().positive().optional(),
      maxConcurrentPerSession: z.number().int().positive().optional(),
      strict: z.boolean().optional(),
      runner: z.enum(['in-process', 'cli']).optional(),
      sqlite: z
        .object({
          path: z.string().min(1),
          encryption: z.object({ secret: z.string().min(1) }).optional(),
          walMode: z.boolean().optional(),
          ttlCleanupIntervalMs: z.number().int().positive().optional(),
        })
        .optional(),
      cliRunnerCommand: z
        .object({
          exe: z.string().min(1),
          args: z.array(z.string()).optional(),
        })
        .optional(),
    })
    .optional(),
} satisfies RawZodShape<FrontMcpBaseMetadata>);

export interface FrontMcpMultiAppMetadata extends FrontMcpBaseMetadata {
  splitByApp?: false;
  auth?: AuthOptionsInput;
}

const frontMcpMultiAppSchema = frontMcpBaseSchema.extend({
  splitByApp: z.literal(false).default(false).describe('If true, each app gets its own scope & basePath.'),
  auth: authOptionsSchema.optional().describe("Configures the server's default authentication provider."),
} satisfies RawZodShape<FrontMcpMultiAppMetadata, FrontMcpBaseMetadata>);

export interface FrontMcpSplitByAppMetadata extends FrontMcpBaseMetadata {
  splitByApp: true;
  auth?: AuthOptionsInput;
}

const frontMcpSplitByAppSchema = frontMcpBaseSchema.extend({
  splitByApp: z.literal(true).describe('If false, apps are grouped under the same scope & basePath.'),
  auth: authOptionsSchema.optional().describe("Configures the server's default authentication provider."),
} satisfies RawZodShape<FrontMcpSplitByAppMetadata, FrontMcpBaseMetadata>);

export type FrontMcpMetadata = FrontMcpMultiAppMetadata | FrontMcpSplitByAppMetadata;

/**
 * Type guard for persistence object shape (simplified - no enabled flag)
 */
function isPersistenceObject(
  value: unknown,
): value is { redis?: unknown; defaultTtlMs?: number } | false | undefined | null {
  if (value === undefined || value === null) return true;
  if (value === false) return true; // Explicitly disabled
  if (typeof value !== 'object') return false;
  const obj = value as Record<string, unknown>;
  // Check optional properties have correct types if present
  if ('defaultTtlMs' in obj && typeof obj['defaultTtlMs'] !== 'number') return false;
  return true;
}

/**
 * Transform function to auto-populate transport.persistence from global redis config.
 * This enables automatic transport session persistence when global redis is configured.
 *
 * New simplified behavior:
 * - `persistence: false` → explicitly disabled
 * - `persistence: { redis?: ... }` → enabled with config
 * - `persistence: undefined` → auto-enable when global redis exists
 *
 * Behavior:
 * - If redis is set AND transport.persistence is not configured → auto-enable with global redis
 * - If transport.persistence is false → respect explicit disable
 * - If transport.persistence.redis is explicitly set → use that config
 * - If transport.persistence is object without redis → use global redis
 */
function applyAutoTransportPersistence<T extends { redis?: unknown; transport?: { persistence?: unknown } }>(
  data: T,
): T {
  // If no global redis config, nothing to auto-enable
  if (!data.redis) return data;

  // Safe access with type guard validation
  const transport = data.transport as { persistence?: unknown } | undefined;
  const rawPersistence = transport?.persistence;

  // Validate persistence shape at runtime (should always pass after Zod validation)
  if (!isPersistenceObject(rawPersistence)) {
    return data; // Invalid shape, don't modify
  }

  // Case 1: persistence explicitly disabled (false) - respect that
  if (rawPersistence === false) {
    return data;
  }

  // Case 2: persistence is an object with explicit redis config - use that
  if (rawPersistence && typeof rawPersistence === 'object' && 'redis' in rawPersistence && rawPersistence.redis) {
    return data;
  }

  // Case 3: persistence is an object without redis - use global redis
  if (rawPersistence && typeof rawPersistence === 'object') {
    return {
      ...data,
      transport: {
        ...transport,
        persistence: {
          ...rawPersistence,
          redis: data.redis,
        },
      },
    };
  }

  // Case 4: persistence not configured at all - auto-enable with global redis
  if (rawPersistence === undefined) {
    return {
      ...data,
      transport: {
        ...transport,
        persistence: {
          redis: data.redis,
        },
      },
    };
  }

  return data;
}

export const frontMcpMetadataSchema = frontMcpMultiAppSchema
  .or(frontMcpSplitByAppSchema)
  .transform(applyAutoTransportPersistence);

/**
 * Lite config parser for CLI mode.
 * Validates only fields needed for tool/resource execution, skipping
 * transport persistence, HTTP, redis, elicitation, ext-apps, sqlite, UI, jobs.
 * This reduces cold-start Zod parsing overhead by ~20-40ms.
 */
const frontMcpLiteSchema = z.object({
  info: serverInfoOptionsSchema,
  // Captured call-site dir (#469) — round-trip through the lite parser too.
  __sourceDir: z.string().optional(),
  // Server-level instructions injected into the MCP `initialize` response.
  // Must round-trip through the lite parser so CLI/MCPB builds preserve it.
  instructions: z.string().optional(),
  providers: z.array(annotatedFrontMcpProvidersSchema).optional().default([]),
  tools: z.array(annotatedFrontMcpToolsSchema).optional().default([]),
  resources: z.array(annotatedFrontMcpResourcesSchema).optional().default([]),
  skills: z.array(annotatedFrontMcpSkillsSchema).optional().default([]),
  plugins: z.array(annotatedFrontMcpPluginsSchema).optional().default([]),
  apps: z.array(annotatedFrontMcpAppSchema),
  serve: z.boolean().optional().default(false),
  splitByApp: z.boolean().optional().default(false),
  auth: authOptionsSchema.optional(),
  logging: loggingOptionsSchema.optional(),
  skillsConfig: skillsConfigOptionsSchema.optional(),
  loader: packageLoaderSchema.optional(),
  // Pass through without deep validation — not used in CLI mode
  http: z.any().optional(),
  redis: z.any().optional(),
  pubsub: z.any().optional(),
  transport: z
    .any()
    .optional()
    .transform((val) => val ?? transportOptionsSchema.parse({})),
  pagination: z.any().optional(),
  elicitation: z.any().optional(),
  extApps: z.any().optional(),
  sqlite: z.any().optional(),
  ui: z.any().optional(),
  jobs: z.any().optional(),
  throttle: z.any().optional(),
  health: z.any().optional(),
  metrics: z.any().optional(),
  // Required by Scope.validateAuthoritiesConfig — when entries declare
  // `authorities` metadata the engine still needs the top-level config
  // to instantiate. Pass-through (`z.any()`) so CLI/MCPB builds work
  // without forcing the full base-schema validation pipeline.
  authorities: z.any().optional(),
  // Output-validation policy (see `frontMcpBaseSchema.output`). CLI mode
  // honors the same `allowNonFinite` switch as the full server.
  output: outputPolicySchema.optional(),
});

/**
 * Parse config with minimal validation for CLI mode.
 * Skips transport auto-persistence and deep validation of unused subsystems.
 */
export function parseFrontMcpConfigLite(input: FrontMcpConfigInput): FrontMcpConfigType {
  return frontMcpLiteSchema.parse(input) as unknown as FrontMcpConfigType;
}

export type FrontMcpMultiAppConfig = z.infer<typeof frontMcpMultiAppSchema>;
export type FrontMcpSplitByAppConfig = z.infer<typeof frontMcpSplitByAppSchema>;

/** Output type after zod parsing (with defaults applied) */
export type FrontMcpConfigType = z.infer<typeof frontMcpMetadataSchema>;

/** Input type for FrontMCP configuration (before zod defaults) */
export type FrontMcpConfigInput = z.input<typeof frontMcpMetadataSchema>;

export interface AppScopeMetadata extends Omit<FrontMcpSplitByAppMetadata, 'splitByApp'> {
  id: string;
  apps: [AppType];
  auth?: AuthOptions;
}

export interface MultiAppScopeMetadata extends FrontMcpMultiAppMetadata {
  id: string;
  apps: AppType[];
}

export type ScopeMetadata = AppScopeMetadata | MultiAppScopeMetadata;
