/**
 * ResourceRegistry - Resource discovery and lookup
 * Handles resource definitions from OrganizationRegistry
 *
 * Features:
 * - Resource discovery by organization
 * - Startup validation (duplicate IDs, model configs, relationships, interface-schema alignment)
 * - Pre-serialization cache for instant API responses
 * - Command View data generation
 */

import type { WorkflowDefinition } from '../../execution/engine/workflow/types'
import type { AgentDefinition } from '../../execution/engine/agent/core/types'
import type { OrganizationModel } from '../../organization-model/types'
import type { ResourceEntry } from '../../organization-model/domains/resources'
import type { SystemEntry } from '../../organization-model/domains/systems'
import { listAllSystems } from '../../organization-model/helpers'
import type {
  ResourceStatus,
  ResourceDefinition,
  ResourceList,
  TriggerDefinition,
  IntegrationDefinition,
  ResourceRelationships,
  ExternalResourceDefinition,
  HumanCheckpointDefinition
} from './types'
import type {
  SerializedOrganizationData,
  SerializedAgentDefinition,
  SerializedWorkflowDefinition,
  CommandViewData
} from './serialized-types'
import { validateDeploymentSpec, validateRelationships } from './validation'
import { serializeAllOrganizations, serializeOrganization } from './serialization'
import { isReservedResourceId } from './reserved'

/** Filter out archived resources from an organization's resource collection */
function filterArchived(org: DeploymentSpec): DeploymentSpec {
  return {
    ...org,
    workflows: org.workflows?.filter((w) => !w.config.archived),
    agents: org.agents?.filter((a) => !a.config.archived),
    triggers: org.triggers?.filter((t) => !t.archived),
    integrations: org.integrations?.filter((i) => !i.archived),
    externalResources: org.externalResources?.filter((e) => !e.archived),
    humanCheckpoints: org.humanCheckpoints?.filter((h) => !h.archived)
  }
}

function summarizeSystem(system: SystemEntry | undefined): ResourceDefinition['system'] {
  if (!system) return undefined

  return {
    id: system.id,
    title: system.label ?? system.title,
    description: system.description,
    kind: system.kind,
    lifecycle: system.lifecycle
  }
}

/**
 * Configuration for a remotely-deployed organization
 *
 * Stored alongside runtime-registered organizations to support
 * worker thread execution branching and credential management.
 */
export interface RemoteOrgConfig {
  /** Supabase Storage path: "{orgId}/{deploymentId}/bundle.js" */
  storagePath: string
  /** Deployment record ID */
  deploymentId: string
  /** OS temp path to bundle -- set after first download, used by worker threads */
  cachedTempPath?: string
  /** Platform tool name -> credential name mapping */
  toolCredentials?: Record<string, string>
  /** SDK version used to deploy this bundle */
  sdkVersion?: string
  /** Deployment version (semver) of the deployed bundle */
  deploymentVersion?: string
}

/**
 * Configuration for a first-class System resource.
 *
 * System resources are owned by the platform, registered under the 'system' org,
 * and execute via the static-bundle loader mode in executeInWorker(). The moduleId
 * maps to an entry in the API's STATIC_MODULE_MAP.
 */
export interface SystemConfig {
  kind: 'static'
  moduleId: string
  /** Always undefined for system resources; present for API compatibility with RemoteOrgConfig consumers */
  sdkVersion?: never
}

/**
 * Organization-specific resource collection
 *
 * Complete manifest of all automation resources for an organization.
 * Used by ResourceRegistry for discovery and Command View for visualization.
 */
export interface DeploymentSpec {
  /** Deployment version (semver) */
  version: string
  /** Optional full Organization Model snapshot used for OM-code validation and deployment persistence */
  organizationModel?: OrganizationModel
  /** Workflow definitions */
  workflows?: WorkflowDefinition[]
  /** Agent definitions */
  agents?: AgentDefinition[]

  // Resource Manifest fields (optional for backwards compatibility)
  /** Trigger definitions - entry points that initiate executions */
  triggers?: TriggerDefinition[]
  /** Integration definitions - external service connections */
  integrations?: IntegrationDefinition[]
  /** Explicit relationship declarations between resources */
  relationships?: ResourceRelationships
  /** External automation resources (n8n, Make, Zapier, etc.) */
  externalResources?: ExternalResourceDefinition[]
  /** Human checkpoint definitions - human decision points in automation */
  humanCheckpoints?: HumanCheckpointDefinition[]
}

/**
 * Organization Registry type
 */
export type OrganizationRegistry = Record<string, DeploymentSpec>

export class ResourceRegistry {
  /**
   * Pre-serialized organization data cache
   * Computed once at construction for static orgs, updated incrementally for runtime orgs
   */
  private serializedCache: Map<string, SerializedOrganizationData>

  /**
   * Per-resource remote configuration (external deployments)
   * Key: "orgName/resourceId", Value: RemoteOrgConfig for that resource.
   * Tracks which individual resources were added at runtime via deploy pipeline.
   * Static and remote resources coexist in the same org.
   */
  private remoteResources = new Map<string, RemoteOrgConfig>()

  /**
   * System configs for first-class platform resources.
   * Key: "orgName/resourceId", Value: SystemConfig.
   * Registered at startup alongside registerStaticResources().
   */
  private systemConfigs = new Map<string, SystemConfig>()

  constructor(private registry: OrganizationRegistry) {
    this.validateRegistry()
    this.validateRelationships()
    this.serializedCache = serializeAllOrganizations(registry)
  }

  /**
   * Validates registry on construction
   * - Checks for duplicate resourceIds within organizations
   * - Validates model configurations against constraints
   * - Validates ExecutionInterface matches inputSchema
   * @throws Error if validation fails
   */
  private validateRegistry(): void {
    for (const [orgName, resources] of Object.entries(this.registry)) {
      validateDeploymentSpec(orgName, resources)
    }
  }

  /**
   * Validates relationship declarations reference valid resources
   * Runs at API server startup - fails fast in development
   * @throws Error if validation fails
   */
  private validateRelationships(): void {
    for (const [orgName, resources] of Object.entries(this.registry)) {
      validateRelationships(orgName, resources)
    }
  }

  /**
   * Get the remote resource IDs currently registered for an organization.
   * Used to validate redeployments against the post-swap state before any
   * live registry mutation occurs.
   */
  private getRemoteResourceIds(orgName: string): Set<string> {
    const prefix = `${orgName}/`
    const remoteIds = new Set<string>()
    for (const key of this.remoteResources.keys()) {
      if (key.startsWith(prefix)) {
        remoteIds.add(key.slice(prefix.length))
      }
    }
    return remoteIds
  }

  /**
   * Build the "static + surviving" baseline for registration validation.
   * On redeploy, this strips the currently remote-owned resources and
   * deployment-owned metadata so validation reflects the state after swap.
   */
  private buildRegistrationBase(orgName: string): DeploymentSpec | undefined {
    const existingOrg = this.registry[orgName]
    if (!existingOrg) return undefined

    const remoteIds = this.getRemoteResourceIds(orgName)
    if (remoteIds.size === 0) return existingOrg

    const relationships = existingOrg.relationships
      ? Object.fromEntries(
          Object.entries(existingOrg.relationships).filter(([resourceId]) => !remoteIds.has(resourceId))
        )
      : undefined

    return {
      ...existingOrg,
      version: existingOrg.version ?? '0.0.0',
      workflows: (existingOrg.workflows ?? []).filter((w) => !remoteIds.has(w.config.resourceId)),
      agents: (existingOrg.agents ?? []).filter((a) => !remoteIds.has(a.config.resourceId)),
      triggers: undefined,
      integrations: undefined,
      humanCheckpoints: undefined,
      externalResources: undefined,
      relationships: relationships && Object.keys(relationships).length > 0 ? relationships : undefined
    }
  }

  /**
   * Validate the registry state that would exist after registration succeeds.
   * This runs before any live mutation so invalid redeploys preserve the
   * currently active remote resources.
   */
  private validateRegistrationCandidate(orgName: string, incoming: DeploymentSpec): void {
    const base = this.buildRegistrationBase(orgName)

    const candidate: DeploymentSpec = base
      ? {
          ...base,
          version: incoming.version ?? base.version ?? '0.0.0',
          organizationModel: incoming.organizationModel ?? base.organizationModel,
          workflows: [...(base.workflows ?? []), ...(incoming.workflows ?? [])],
          agents: [...(base.agents ?? []), ...(incoming.agents ?? [])],
          triggers: incoming.triggers,
          integrations: incoming.integrations,
          humanCheckpoints: incoming.humanCheckpoints,
          externalResources: incoming.externalResources,
          relationships: incoming.relationships
            ? {
                ...(base.relationships ?? {}),
                ...incoming.relationships
              }
            : base.relationships
        }
      : {
          ...incoming,
          version: incoming.version ?? '0.0.0'
        }

    validateDeploymentSpec(orgName, candidate)
    validateRelationships(orgName, candidate)
  }

  /**
   * Get a resource definition by ID
   * Returns full definition (WorkflowDefinition or AgentDefinition)
   * Check definition.config.type to determine if it's a workflow or agent
   */
  getResourceDefinition(organizationName: string, resourceId: string): WorkflowDefinition | AgentDefinition | null {
    const orgResources = this.registry[organizationName]
    if (!orgResources) return null

    // Check workflows first
    const workflow = orgResources.workflows?.find((w) => w.config.resourceId === resourceId)
    if (workflow) return workflow

    // Check agents
    const agent = orgResources.agents?.find((a) => a.config.resourceId === resourceId)
    if (agent) return agent

    return null
  }

  /**
   * List all resources for an organization
   * Returns ResourceDefinition metadata (not full definitions)
   *
   * All resources are returned regardless of server environment.
   * Pass an explicit `environment` filter to get only 'dev' or 'prod' resources.
   */
  listResourcesForOrganization(organizationName: string, environment?: ResourceStatus): ResourceList {
    const orgResources = this.registry[organizationName]
    if (!orgResources) {
      return {
        workflows: [],
        agents: [],
        total: 0,
        organizationName,
        environment
      }
    }

    const resourcesById = new Map(Object.values(orgResources.organizationModel?.resources ?? {}).map((r) => [r.id, r]))
    // Build a path-keyed map that includes nested systems (DFS via listAllSystems).
    // resource.systemPath is the dot-separated system path used as the lookup key.
    const omSystems = orgResources.organizationModel?.systems
    const systemsByPath = new Map<string, SystemEntry>(
      omSystems
        ? listAllSystems({ systems: omSystems } as OrganizationModel).map(({ path, system }) => [path, system])
        : []
    )
    const getGovernanceMetadata = (
      resourceId: string,
      descriptor: ResourceEntry | undefined
    ): Pick<ResourceDefinition, 'systemPath' | 'system' | 'governanceStatus'> => {
      const resource = descriptor ?? resourcesById.get(resourceId)
      if (!resource) return {}

      return {
        systemPath: resource.systemPath,
        system: summarizeSystem(systemsByPath.get(resource.systemPath)),
        governanceStatus: resource.status
      }
    }

    // Map workflows to ResourceDefinition metadata and filter by environment
    const workflows: ResourceDefinition[] = (orgResources.workflows || [])
      .map((def) => ({
        resourceId: def.config.resourceId,
        name: def.config.name,
        description: def.config.description,
        version: def.config.version,
        type: def.config.type,
        status: def.config.status,
        links: def.config.links,
        category: def.config.category,
        origin: this.remoteResources.has(`${organizationName}/${def.config.resourceId}`)
          ? ('remote' as const)
          : ('local' as const),
        ...getGovernanceMetadata(def.config.resourceId, def.config.resource)
      }))
      .filter((resource) => !environment || resource.status === environment)

    // Map agents to ResourceDefinition metadata and filter by environment
    const agents: ResourceDefinition[] = (orgResources.agents || [])
      .map((def) => ({
        resourceId: def.config.resourceId,
        name: def.config.name,
        description: def.config.description,
        version: def.config.version,
        type: def.config.type,
        status: def.config.status,
        links: def.config.links,
        category: def.config.category,
        sessionCapable: def.config.sessionCapable ?? false,
        origin: this.remoteResources.has(`${organizationName}/${def.config.resourceId}`)
          ? ('remote' as const)
          : ('local' as const),
        ...getGovernanceMetadata(def.config.resourceId, def.config.resource)
      }))
      .filter((resource) => !environment || resource.status === environment)

    return {
      workflows,
      agents,
      total: workflows.length + agents.length,
      organizationName,
      environment
    }
  }

  /**
   * List all resources from all organizations
   * NOTE: For debugging only - returns raw registry data
   */
  listAllResources(): OrganizationRegistry {
    return this.registry
  }

  // ============================================================================
  // Runtime Organization Registration (External Deployments)
  // ============================================================================

  /**
   * Register external resources at runtime
   *
   * Called during deploy pipeline when an external developer deploys their bundle.
   * Merges the incoming stub definitions into the org's registry and stores
   * per-resource remote config for worker thread execution branching.
   *
   * Static and remote resources coexist in the same org. If the org already
   * has static resources, the incoming remote resources are merged alongside them.
   * If redeploying (some resources already registered as remote for this org),
   * the previous remote resources are unregistered first.
   *
   * @param orgName - Organization name (used as registry key)
   * @param org - Stub resource definitions (workflows/agents with placeholder handlers)
   * @param remote - Remote configuration (bundle path, deployment ID, env vars)
   * @throws Error if incoming resourceId conflicts with a static resource
   * @throws Error if incoming deployment contains duplicate resourceIds
   */
  registerOrganization(orgName: string, org: DeploymentSpec, remote: RemoteOrgConfig): void {
    // Filter out archived resources before any processing
    org = filterArchived(org)

    // Collect all incoming resource IDs for conflict checking
    const incomingWorkflowIds = (org.workflows ?? []).map((w) => w.config.resourceId)
    const incomingAgentIds = (org.agents ?? []).map((a) => a.config.resourceId)
    const incomingIds = [...incomingWorkflowIds, ...incomingAgentIds]

    // Check for intra-deployment duplicates
    const seen = new Set<string>()
    for (const id of incomingIds) {
      if (seen.has(id)) {
        throw new Error(`Duplicate resource ID '${id}' in deployment. Each resource must have a unique ID.`)
      }
      seen.add(id)
    }

    // Check for reserved resource IDs (cannot be claimed by external deployments)
    for (const id of incomingIds) {
      if (isReservedResourceId(id)) {
        throw new Error(
          `Resource ID '${id}' is reserved for platform use. External deployments cannot use reserved resource IDs.`
        )
      }
    }

    // Check for conflicts against the static/surviving org state.
    // On redeploy, current remote resources are excluded so a deployment can
    // legitimately replace its own resource IDs without colliding with itself.
    const validationBase = this.buildRegistrationBase(orgName)
    if (validationBase) {
      const staticWorkflowIds = new Set((validationBase.workflows ?? []).map((w) => w.config.resourceId))
      const staticAgentIds = new Set((validationBase.agents ?? []).map((a) => a.config.resourceId))

      for (const id of incomingIds) {
        if (staticWorkflowIds.has(id) || staticAgentIds.has(id)) {
          throw new Error(
            `Resource '${id}' already exists in '${orgName}' as an internal resource. External deployments cannot override internal resources.`
          )
        }
      }
    }

    // Validate the merged deployment shape before mutating the live registry.
    // This keeps the current deployment intact if a redeploy introduces
    // invalid relationships or other registry-level validation failures.
    this.validateRegistrationCandidate(orgName, org)

    // If redeploying (some resources already registered as remote for this org), clean up only
    // after validation succeeds so a failed redeploy preserves the current remote resources.
    if (this.isRemote(orgName)) {
      this.unregisterOrganization(orgName)
    }

    // Merge incoming resources into the org (or create new org entry)
    const existingOrg = this.registry[orgName]
    if (existingOrg) {
      existingOrg.organizationModel = org.organizationModel ?? existingOrg.organizationModel
      existingOrg.workflows = [...(existingOrg.workflows ?? []), ...(org.workflows ?? [])]
      existingOrg.agents = [...(existingOrg.agents ?? []), ...(org.agents ?? [])]
      // Deployment-owned metadata: replace entirely (unregister cleared these)
      existingOrg.triggers = org.triggers
      existingOrg.integrations = org.integrations
      existingOrg.humanCheckpoints = org.humanCheckpoints
      existingOrg.externalResources = org.externalResources
      if (org.relationships) {
        existingOrg.relationships = {
          ...(existingOrg.relationships ?? {}),
          ...org.relationships
        }
      }
    } else {
      this.registry[orgName] = org
    }

    // Populate per-resource remote config entries
    for (const id of incomingIds) {
      this.remoteResources.set(`${orgName}/${id}`, remote)
    }

    // Rebuild serialized cache for the full merged org
    this.serializedCache.set(orgName, serializeOrganization(this.registry[orgName]))
  }

  /**
   * Patch serialized cache with pre-serialized schemas from an external manifest.
   *
   * External deployments use stub definitions with z.any() schemas (never called).
   * The manifest carries the real schemas as pre-serialized JSON Schema from the worker.
   * This method patches those into the serialized cache so describe/CLI display them.
   *
   * @param orgName - Organization name
   * @param manifestSchemas - Map of resourceId -> { contract, steps } with JSON Schema
   */
  patchManifestSchemas(
    orgName: string,
    manifestSchemas: Array<{
      resourceId: string
      type: 'workflow' | 'agent'
      contract?: { inputSchema?: object; outputSchema?: object }
      steps?: Array<{ id: string; inputSchema?: object; outputSchema?: object }>
    }>
  ): void {
    const cache = this.serializedCache.get(orgName)
    if (!cache) return

    for (const entry of manifestSchemas) {
      if (entry.type === 'workflow') {
        const def = cache.definitions.workflows.get(entry.resourceId)
        if (!def) continue

        // Patch contract schemas
        if (entry.contract?.inputSchema) def.contract.inputSchema = entry.contract.inputSchema
        if (entry.contract?.outputSchema) def.contract.outputSchema = entry.contract.outputSchema

        // Patch step schemas
        if (entry.steps && def.steps) {
          for (const stepPatch of entry.steps) {
            const step = def.steps.find((s) => s.id === stepPatch.id)
            if (!step) continue
            if (stepPatch.inputSchema) step.inputSchema = stepPatch.inputSchema
            if (stepPatch.outputSchema) step.outputSchema = stepPatch.outputSchema
          }
        }
      } else if (entry.type === 'agent') {
        const def = cache.definitions.agents.get(entry.resourceId)
        if (!def) continue
        if (entry.contract?.inputSchema) def.contract.inputSchema = entry.contract.inputSchema
        if (entry.contract?.outputSchema) def.contract.outputSchema = entry.contract.outputSchema
      }
    }
  }

  /**
   * Register built-in platform resources (static, local execution)
   *
   * Unlike registerOrganization(), these resources:
   * - Do NOT have remote config (execute in-process, not in worker threads)
   * - Are NOT removed by unregisterOrganization() (persist across redeployments)
   * - Use reserved resource IDs that external deployments cannot claim
   *
   * @param orgName - Organization name
   * @param org - Resource definitions with real handlers (not stubs)
   */
  registerStaticResources(orgName: string, org: DeploymentSpec): void {
    // Filter out archived resources before any processing
    org = filterArchived(org)

    const incomingWorkflowIds = (org.workflows ?? []).map((w) => w.config.resourceId)
    const incomingAgentIds = (org.agents ?? []).map((a) => a.config.resourceId)
    const incomingIds = [...incomingWorkflowIds, ...incomingAgentIds]

    // Check for duplicates within incoming resources
    const seen = new Set<string>()
    for (const id of incomingIds) {
      if (seen.has(id)) {
        throw new Error(`Duplicate resource ID '${id}' in static resources.`)
      }
      seen.add(id)
    }

    // Check for conflicts with existing resources in this org
    const existingOrg = this.registry[orgName]
    if (existingOrg) {
      const existingWorkflowIds = new Set((existingOrg.workflows ?? []).map((w) => w.config.resourceId))
      const existingAgentIds = new Set((existingOrg.agents ?? []).map((a) => a.config.resourceId))

      for (const id of incomingIds) {
        if (existingWorkflowIds.has(id) || existingAgentIds.has(id)) {
          throw new Error(`Static resource '${id}' conflicts with existing resource in '${orgName}'.`)
        }
      }
    }

    // Merge into registry (no remote config = local execution path)
    if (existingOrg) {
      existingOrg.workflows = [...(existingOrg.workflows ?? []), ...(org.workflows ?? [])]
      existingOrg.agents = [...(existingOrg.agents ?? []), ...(org.agents ?? [])]
    } else {
      this.registry[orgName] = org
    }

    // Rebuild serialized cache
    this.serializedCache.set(orgName, serializeOrganization(this.registry[orgName]))
  }

  /**
   * Unregister runtime-registered resources for an organization
   *
   * Removes only resources that were registered at runtime (via registerOrganization).
   * Static resources loaded at startup are preserved. If the org still has static
   * resources after removal, the serialization cache is rebuilt. If no resources
   * remain, the org is fully removed from the registry.
   * No-op if the org has no remote resources.
   *
   * @param orgName - Organization name to unregister remote resources from
   */
  unregisterOrganization(orgName: string): void {
    // Find all remote resource keys for this org
    const prefix = `${orgName}/`
    const remoteIds = new Set<string>()
    for (const key of this.remoteResources.keys()) {
      if (key.startsWith(prefix)) {
        remoteIds.add(key.slice(prefix.length))
        this.remoteResources.delete(key)
      }
    }

    // No-op if org had no remote resources
    if (remoteIds.size === 0) return

    const orgResources = this.registry[orgName]
    if (!orgResources) return

    // Remove remote resources from the org's arrays
    orgResources.workflows = (orgResources.workflows ?? []).filter((w) => !remoteIds.has(w.config.resourceId))
    orgResources.agents = (orgResources.agents ?? []).filter((a) => !remoteIds.has(a.config.resourceId))

    // Remove deployment-owned metadata (triggers, integrations, checkpoints, external resources)
    // These are always fully owned by the deployment — replaced on each deploy
    orgResources.triggers = undefined
    orgResources.integrations = undefined
    orgResources.humanCheckpoints = undefined
    orgResources.externalResources = undefined

    // Remove relationship entries for remote resource IDs
    if (orgResources.relationships) {
      for (const id of remoteIds) {
        delete orgResources.relationships[id]
      }
      if (Object.keys(orgResources.relationships).length === 0) {
        delete orgResources.relationships
      }
    }

    // If the org still has static resources, rebuild cache; otherwise fully remove
    // (triggers, integrations, checkpoints, externalResources were cleared above)
    const remaining = (orgResources.workflows?.length ?? 0) + (orgResources.agents?.length ?? 0)

    if (remaining > 0) {
      this.serializedCache.set(orgName, serializeOrganization(orgResources))
    } else {
      delete this.registry[orgName]
      this.serializedCache.delete(orgName)
    }
  }

  /**
   * Get remote configuration for a specific resource.
   *
   * Returns RemoteOrgConfig for externally-deployed resources, SystemConfig for
   * first-class platform resources, or null for static in-process resources.
   * Used by the execution coordinator to determine the execution path.
   *
   * @param orgName - Organization name
   * @param resourceId - Resource ID
   * @returns Remote or System config, or null
   */
  getRemoteConfig(orgName: string, resourceId: string): RemoteOrgConfig | SystemConfig | null {
    const key = `${orgName}/${resourceId}`
    return this.remoteResources.get(key) ?? this.systemConfigs.get(key) ?? null
  }

  /**
   * Register a System config for a first-class platform resource.
   *
   * Called at startup alongside registerStaticResources() so that
   * getRemoteConfig('system', resourceId) returns truthy and the execution
   * coordinator routes the resource through the worker-thread path.
   *
   * @param orgName - Organization name (typically 'system')
   * @param resourceId - Resource ID
   * @param config - SystemConfig with kind:'static' and moduleId
   */
  registerSystemConfig(orgName: string, resourceId: string, config: SystemConfig): void {
    this.systemConfigs.set(`${orgName}/${resourceId}`, config)
  }

  /**
   * Check if an organization has any remote (externally deployed) resources
   *
   * @param orgName - Organization name
   * @returns true if the org has at least one runtime-registered resource
   */
  isRemote(orgName: string): boolean {
    const prefix = `${orgName}/`
    for (const key of this.remoteResources.keys()) {
      if (key.startsWith(prefix)) return true
    }
    return false
  }

  /**
   * Get the remote config for any resource in an organization.
   * Used when the specific resource ID is unknown (e.g., to clean up a
   * temp file before unregistering an org -- all resources share one config).
   *
   * @param orgName - Organization name
   * @returns Remote config or null if org has no remote resources
   */
  getAnyRemoteConfig(orgName: string): RemoteOrgConfig | null {
    const prefix = `${orgName}/`
    for (const [key, config] of this.remoteResources) {
      if (key.startsWith(prefix)) return config
    }
    return null
  }

  /**
   * Get statistics about remotely-deployed resources
   * Used by the health endpoint for platform-wide deployment visibility.
   */
  getRemoteStats(): { activeOrgs: number; totalResources: number } {
    const orgs = new Set<string>()
    for (const key of this.remoteResources.keys()) {
      const orgName = key.split('/')[0]
      orgs.add(orgName)
    }
    return {
      activeOrgs: orgs.size,
      totalResources: this.remoteResources.size
    }
  }

  // ============================================================================
  // Resource Manifest Accessors
  // ============================================================================

  /**
   * Get triggers for an organization
   * @param organizationName - Organization name
   * @returns Array of trigger definitions (empty if none defined)
   */
  getTriggers(organizationName: string): TriggerDefinition[] {
    return this.registry[organizationName]?.triggers ?? []
  }

  /**
   * Get integrations for an organization
   * @param organizationName - Organization name
   * @returns Array of integration definitions (empty if none defined)
   */
  getIntegrations(organizationName: string): IntegrationDefinition[] {
    return this.registry[organizationName]?.integrations ?? []
  }

  /**
   * Get resource relationships for an organization
   * @param organizationName - Organization name
   * @returns Resource relationships map (undefined if none defined)
   */
  getRelationships(organizationName: string): ResourceRelationships | undefined {
    return this.registry[organizationName]?.relationships
  }

  /**
   * Get a specific trigger by ID
   * @param organizationName - Organization name
   * @param triggerId - Trigger ID
   * @returns Trigger definition or null if not found
   */
  getTrigger(organizationName: string, triggerId: string): TriggerDefinition | null {
    const triggers = this.getTriggers(organizationName)
    return triggers.find((t) => t.resourceId === triggerId) ?? null
  }

  /**
   * Get a specific integration by ID
   * @param organizationName - Organization name
   * @param integrationId - Integration ID
   * @returns Integration definition or null if not found
   */
  getIntegration(organizationName: string, integrationId: string): IntegrationDefinition | null {
    const integrations = this.getIntegrations(organizationName)
    return integrations.find((i) => i.resourceId === integrationId) ?? null
  }

  /**
   * Get external resources for an organization
   * @param organizationName - Organization name
   * @returns Array of external resource definitions (empty if none defined)
   */
  getExternalResources(organizationName: string): ExternalResourceDefinition[] {
    return this.registry[organizationName]?.externalResources ?? []
  }

  /**
   * Get a specific external resource by ID
   * @param organizationName - Organization name
   * @param externalId - External resource ID
   * @returns External resource definition or null if not found
   */
  getExternalResource(organizationName: string, externalId: string): ExternalResourceDefinition | null {
    const externalResources = this.getExternalResources(organizationName)
    return externalResources.find((e) => e.resourceId === externalId) ?? null
  }

  /**
   * Get human checkpoints for an organization
   * @param organizationName - Organization name
   * @returns Array of human checkpoint definitions (empty if none defined)
   */
  getHumanCheckpoints(organizationName: string): HumanCheckpointDefinition[] {
    return this.registry[organizationName]?.humanCheckpoints ?? []
  }

  /**
   * Get a specific human checkpoint by ID
   * @param organizationName - Organization name
   * @param humanCheckpointId - Human checkpoint ID
   * @returns Human checkpoint definition or null if not found
   */
  getHumanCheckpoint(organizationName: string, humanCheckpointId: string): HumanCheckpointDefinition | null {
    const humanCheckpoints = this.getHumanCheckpoints(organizationName)
    return humanCheckpoints.find((hc) => hc.resourceId === humanCheckpointId) ?? null
  }

  // ============================================================================
  // Serialized Data Access (Pre-computed at Startup)
  // ============================================================================

  /**
   * Get serialized resource definition (instant lookup)
   * Use for API responses - returns pre-computed JSON-safe structure
   *
   * @param organizationName - Organization name
   * @param resourceId - Resource ID
   * @returns Serialized definition or null if not found
   */
  getSerializedDefinition(
    organizationName: string,
    resourceId: string
  ): SerializedAgentDefinition | SerializedWorkflowDefinition | null {
    const cache = this.serializedCache.get(organizationName)
    if (!cache) return null

    return cache.definitions.agents.get(resourceId) ?? cache.definitions.workflows.get(resourceId) ?? null
  }

  /**
   * Get resource list for organization (instant lookup)
   * Use for /resources endpoint - returns pre-computed ResourceDefinition array
   *
   * @param organizationName - Organization name
   * @returns Resource list with workflows, agents, and total count
   */
  getResourceList(organizationName: string): {
    workflows: ResourceDefinition[]
    agents: ResourceDefinition[]
    total: number
  } {
    const cache = this.serializedCache.get(organizationName)
    if (!cache) {
      return { workflows: [], agents: [], total: 0 }
    }
    return cache.resources
  }

  /**
   * Get Command View data for organization (instant lookup)
   * Use for /command-view endpoint - returns complete graph data
   *
   * @param organizationName - Organization name
   * @returns Command View data with nodes and edges
   */
  getCommandViewData(organizationName: string): CommandViewData {
    const cache = this.serializedCache.get(organizationName)
    if (!cache) {
      return {
        workflows: [],
        agents: [],
        triggers: [],
        integrations: [],
        externalResources: [],
        humanCheckpoints: [],
        edges: []
      }
    }
    return cache.commandView
  }
}
