import {
  compileOrganizationOntology,
  formatOntologyId,
  parseOntologyId,
  type OntologyActionType,
  type OntologyCatalogType,
  type OntologyId,
  type OntologyScope,
  type ResolvedOntologyIndex
} from '../../organization-model/ontology'
import type { OrganizationModel } from '../../organization-model/types'
import type { ResourceEntry } from '../../organization-model/domains/resources'
import {
  SYSTEM_INTERFACE_PROFILES,
  SYSTEM_INTERFACE_READINESS_PROFILES
} from '../../organization-model/domains/systems'
import type { OmTopologyRelationship } from '../../organization-model/domains/topology'
import { type LeadGenStageCatalogEntry, type StatefulStateDefinition } from '../../organization-model/domains/sales'
import { getSystem } from '../../organization-model/helpers'
import { getLeadGenStageCatalog } from '../../organization-model/migration-helpers'

export const LEAD_GEN_API_INTERFACE = SYSTEM_INTERFACE_PROFILES[0]

export const CRM_API_INTERFACE = SYSTEM_INTERFACE_PROFILES[1]

export const LEAD_GEN_CRM_HANDOFF_INTERFACE = SYSTEM_INTERFACE_PROFILES[2]

const LEAD_GEN_API_READINESS_LABEL = LEAD_GEN_API_INTERFACE.readinessProfile
const CRM_API_READINESS_LABEL = CRM_API_INTERFACE.readinessProfile
const LEAD_GEN_CRM_HANDOFF_READINESS_LABEL = LEAD_GEN_CRM_HANDOFF_INTERFACE.readinessProfile

export const LEAD_GEN_LIST_OBJECT_ONTOLOGY_ID = formatOntologyId({
  scope: 'sales.lead-gen',
  kind: 'object',
  localId: 'list'
})

export const LEAD_GEN_COMPANY_OBJECT_ONTOLOGY_ID = formatOntologyId({
  scope: 'sales.lead-gen',
  kind: 'object',
  localId: 'company'
})

export const LEAD_GEN_CONTACT_OBJECT_ONTOLOGY_ID = formatOntologyId({
  scope: 'sales.lead-gen',
  kind: 'object',
  localId: 'contact'
})

export const LEAD_GEN_BUILD_TEMPLATE_CATALOG_ONTOLOGY_ID = formatOntologyId({
  scope: 'sales.lead-gen',
  kind: 'catalog',
  localId: 'build-template'
})

export const LEAD_GEN_COMPANY_STAGE_CATALOG_ONTOLOGY_ID = formatOntologyId({
  scope: 'sales.lead-gen',
  kind: 'catalog',
  localId: 'company-stage'
})

export const LEAD_GEN_CONTACT_STAGE_CATALOG_ONTOLOGY_ID = formatOntologyId({
  scope: 'sales.lead-gen',
  kind: 'catalog',
  localId: 'contact-stage'
})

export const CRM_PIPELINE_CATALOG_ONTOLOGY_ID = formatOntologyId({
  scope: 'sales.crm',
  kind: 'catalog',
  localId: 'crm.pipeline'
})

export const LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID = formatOntologyId({
  scope: 'sales.lead-gen',
  kind: 'catalog',
  localId: 'lead-gen.stage-catalog'
})

type BusinessOntologyValidationIndexBase = {
  ontology: ResolvedOntologyIndex
  actionTypesByLegacyId: Record<string, OntologyActionType>
}

export type LeadGenApiOntologyValidationIndex = BusinessOntologyValidationIndexBase & {
  leadGenStageCatalog: OntologyCatalogType
}

export type CrmApiOntologyValidationIndex = BusinessOntologyValidationIndexBase & {
  crmPipelineCatalog: OntologyCatalogType
}

export type LeadGenCrmHandoffOntologyValidationIndex = BusinessOntologyValidationIndexBase & {
  leadGenStageCatalog: OntologyCatalogType
  crmPipelineCatalog: OntologyCatalogType
  bridgeGrant?: OmTopologyRelationship
}

export type BusinessOntologyValidationIndex = LeadGenApiOntologyValidationIndex & CrmApiOntologyValidationIndex

export type SystemInterfaceReadinessIssueFamily =
  | 'SYSTEM_INTERFACE_MISSING'
  | 'SYSTEM_INTERFACE_DISABLED'
  | 'SYSTEM_INTERFACE_INVALID'
  | 'SYSTEM_INTERFACE_NOT_READY'
  | 'SYSTEM_BRIDGE_NOT_READY'
  /**
   * The deployed OM snapshot hash does not match the canonical source hash.
   * Redeploy with `pnpm operations:deploy` to refresh the snapshot.
   */
  | 'STALE_OM_SNAPSHOT'

export interface SystemInterfaceReadinessIssue {
  family: SystemInterfaceReadinessIssueFamily
  code: string
  path?: string
  ref?: string
  message: string
}

export interface SystemInterfaceReadinessRequest {
  systemPath: string
  interfaceKey: string
}

export interface SystemInterfaceReadinessResult {
  ready: boolean
  systemPath: string
  interfaceKey: string
  readinessProfile?: string
  scopedResourceIds: string[]
  issues: SystemInterfaceReadinessIssue[]
}

export class SystemInterfaceReadinessError extends Error {
  public readonly code: SystemInterfaceReadinessIssueFamily
  public readonly statusCode = 503
  public readonly systemPath: string
  public readonly interfaceKey: string
  public readonly readinessProfile?: string
  public readonly issues: SystemInterfaceReadinessIssue[]

  constructor(result: SystemInterfaceReadinessResult) {
    const firstIssue = result.issues[0]
    super(formatInterfaceReadinessFailure(result))
    this.name = 'SystemInterfaceReadinessError'
    this.code = firstIssue?.family ?? 'SYSTEM_INTERFACE_NOT_READY'
    this.systemPath = result.systemPath
    this.interfaceKey = result.interfaceKey
    this.readinessProfile = result.readinessProfile
    this.issues = result.issues
  }
}

function createLeadGenStageCatalog(model: OrganizationModel): OntologyCatalogType {
  return {
    id: LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID,
    label: 'Lead Gen Processing Stages',
    ownerSystemId: 'sales.lead-gen',
    kind: 'processing-stage-catalog',
    entries: Object.fromEntries(
      Object.entries(getLeadGenStageCatalog(model)).map(([key, entry]) => [
        key,
        {
          ...entry
        }
      ])
    ),
    legacyCatalogKey: 'LEAD_GEN_STAGE_CATALOG'
  }
}

function isPlainRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
}

function addReadinessIssue(
  issues: SystemInterfaceReadinessIssue[],
  family: SystemInterfaceReadinessIssueFamily,
  code: string,
  message: string,
  details: Pick<SystemInterfaceReadinessIssue, 'path' | 'ref'> = {}
): void {
  issues.push({
    family,
    code,
    message,
    ...details
  })
}

function formatInterfaceIdentity(systemPath: string, interfaceKey: string): string {
  return `${systemPath}/${interfaceKey}`
}

function profileForInterface(systemPath: string, interfaceKey: string, readinessProfile?: string): string {
  return readinessProfile ?? `${systemPath}.${interfaceKey}`
}

function readinessMarkerPath(context: { systemPath: string; interfaceKey: string }): string {
  return context.interfaceKey === 'api'
    ? `systems.${context.systemPath}.apiInterface`
    : `systems.${context.systemPath}.derivedCrmHandoffReadiness`
}

function formatInterfaceReadinessFailure(result: SystemInterfaceReadinessResult): string {
  const identity = formatInterfaceIdentity(result.systemPath, result.interfaceKey)
  const issueSummary = result.issues.map((issue) => `${issue.family}:${issue.code}: ${issue.message}`).join('; ')
  return `${identity} readiness failed${result.readinessProfile ? ` (${result.readinessProfile})` : ''}: ${issueSummary}`
}

function formatSupportedReadinessProfiles(): string {
  return SYSTEM_INTERFACE_READINESS_PROFILES.map((profile) => `"${profile}"`).join(', ')
}

function throwIfInterfaceNotReady(result: SystemInterfaceReadinessResult): void {
  if (result.ready) return
  throw new SystemInterfaceReadinessError(result)
}

function getActiveScopedResources(
  model: OrganizationModel,
  resourceIds: string[],
  issues: SystemInterfaceReadinessIssue[],
  context: { systemPath: string; interfaceKey: string }
): ResourceEntry[] {
  const resources: ResourceEntry[] = []

  for (const [index, resourceId] of resourceIds.entries()) {
    const resource = model.resources?.[resourceId]
    const path = `${readinessMarkerPath(context)}.resourceIds.${index}`
    if (resource === undefined) {
      addReadinessIssue(
        issues,
        'SYSTEM_INTERFACE_NOT_READY',
        'missing-resource',
        `System Interface "${formatInterfaceIdentity(context.systemPath, context.interfaceKey)}" scopes missing resource "${resourceId}".`,
        { path, ref: resourceId }
      )
      continue
    }

    if (resource.systemPath !== context.systemPath) {
      addReadinessIssue(
        issues,
        'SYSTEM_INTERFACE_INVALID',
        'resource-system-mismatch',
        `System Interface "${formatInterfaceIdentity(context.systemPath, context.interfaceKey)}" scopes resource "${resourceId}" from system "${resource.systemPath}".`,
        { path, ref: resourceId }
      )
      continue
    }

    if (resource.status !== 'active') {
      addReadinessIssue(
        issues,
        'SYSTEM_INTERFACE_NOT_READY',
        'inactive-resource',
        `System Interface "${formatInterfaceIdentity(context.systemPath, context.interfaceKey)}" scopes inactive resource "${resourceId}".`,
        { path, ref: resourceId }
      )
      continue
    }

    resources.push(resource)
  }

  return resources
}

function resourceBindingIds(
  resources: ResourceEntry[],
  key: 'reads' | 'writes' | 'usesCatalogs' | 'actions' | 'emits'
): Set<string> {
  return new Set(resources.flatMap((resource) => resource.ontology?.[key] ?? []))
}

function requireScopedBinding(
  issues: SystemInterfaceReadinessIssue[],
  boundIds: Set<string>,
  bindingKey: 'reads' | 'writes' | 'usesCatalogs' | 'actions' | 'emits',
  ontologyId: string,
  context: { systemPath: string; interfaceKey: string }
): void {
  if (boundIds.has(ontologyId)) return

  addReadinessIssue(
    issues,
    'SYSTEM_INTERFACE_NOT_READY',
    'missing-resource-binding',
    `System Interface "${formatInterfaceIdentity(context.systemPath, context.interfaceKey)}" has no active scoped resource with ontology.${bindingKey} binding "${ontologyId}".`,
    { path: `${readinessMarkerPath(context)}.resourceIds`, ref: ontologyId }
  )
}

function ontologyOwnerMatches(
  ontologyId: string,
  expectedSystemPath: string,
  catalog: OntologyCatalogType | undefined
): boolean {
  if (catalog?.ownerSystemId !== undefined) return catalog.ownerSystemId === expectedSystemPath
  return parseOntologyId(ontologyId).scope === expectedSystemPath
}

function requireObjectReadiness(
  issues: SystemInterfaceReadinessIssue[],
  index: BusinessOntologyValidationIndexBase,
  objectId: OntologyId,
  context: { systemPath: string; interfaceKey: string }
): void {
  const object = index.ontology.objectTypes[objectId]
  if (object === undefined) {
    addReadinessIssue(
      issues,
      'SYSTEM_INTERFACE_NOT_READY',
      'missing-object',
      `System Interface "${formatInterfaceIdentity(context.systemPath, context.interfaceKey)}" is missing required object type "${objectId}".`,
      { ref: objectId }
    )
    return
  }

  const ownerSystemId = object.ownerSystemId ?? parseOntologyId(objectId).scope
  if (ownerSystemId !== context.systemPath) {
    addReadinessIssue(
      issues,
      'SYSTEM_INTERFACE_INVALID',
      'foreign-object',
      `System Interface "${formatInterfaceIdentity(context.systemPath, context.interfaceKey)}" requires object "${objectId}" owned by "${ownerSystemId}".`,
      { ref: objectId }
    )
  }
}

function requireCatalogReadiness(
  issues: SystemInterfaceReadinessIssue[],
  index: BusinessOntologyValidationIndexBase,
  catalogId: OntologyId,
  context: { systemPath: string; interfaceKey: string; allowForeignOwner?: boolean }
): OntologyCatalogType | undefined {
  const catalog = index.ontology.catalogTypes[catalogId]
  if (catalog === undefined) {
    addReadinessIssue(
      issues,
      'SYSTEM_INTERFACE_NOT_READY',
      'missing-catalog',
      `System Interface "${formatInterfaceIdentity(context.systemPath, context.interfaceKey)}" is missing required catalog "${catalogId}".`,
      { ref: catalogId }
    )
    return undefined
  }

  if (context.allowForeignOwner !== true && !ontologyOwnerMatches(catalogId, context.systemPath, catalog)) {
    addReadinessIssue(
      issues,
      'SYSTEM_INTERFACE_INVALID',
      'foreign-catalog',
      `System Interface "${formatInterfaceIdentity(context.systemPath, context.interfaceKey)}" requires catalog "${catalogId}" owned by "${catalog.ownerSystemId ?? parseOntologyId(catalogId).scope}".`,
      { ref: catalogId }
    )
  }

  if (Object.keys(getCatalogEntries(catalog)).length === 0) {
    addReadinessIssue(
      issues,
      'SYSTEM_INTERFACE_NOT_READY',
      'empty-catalog',
      `System Interface "${formatInterfaceIdentity(context.systemPath, context.interfaceKey)}" requires catalog entries for "${catalogId}".`,
      { ref: catalogId }
    )
  }

  return catalog
}

function requireLeadGenInterfaceReadiness(
  issues: SystemInterfaceReadinessIssue[],
  index: BusinessOntologyValidationIndexBase,
  resources: ResourceEntry[],
  context: { systemPath: string; interfaceKey: string }
): OntologyCatalogType | undefined {
  const reads = resourceBindingIds(resources, 'reads')
  const catalogs = resourceBindingIds(resources, 'usesCatalogs')

  for (const objectId of [
    LEAD_GEN_LIST_OBJECT_ONTOLOGY_ID,
    LEAD_GEN_COMPANY_OBJECT_ONTOLOGY_ID,
    LEAD_GEN_CONTACT_OBJECT_ONTOLOGY_ID
  ]) {
    requireObjectReadiness(issues, index, objectId, context)
    requireScopedBinding(issues, reads, 'reads', objectId, context)
  }

  for (const catalogId of [
    LEAD_GEN_BUILD_TEMPLATE_CATALOG_ONTOLOGY_ID,
    LEAD_GEN_COMPANY_STAGE_CATALOG_ONTOLOGY_ID,
    LEAD_GEN_CONTACT_STAGE_CATALOG_ONTOLOGY_ID
  ]) {
    requireCatalogReadiness(issues, index, catalogId, context)
    requireScopedBinding(issues, catalogs, 'usesCatalogs', catalogId, context)
  }

  const templateStepCatalog = findLeadGenTemplateStepCatalog(index)
  if (templateStepCatalog === undefined) {
    addReadinessIssue(
      issues,
      'SYSTEM_INTERFACE_NOT_READY',
      'missing-template-step-catalog',
      `System Interface "${formatInterfaceIdentity(context.systemPath, context.interfaceKey)}" is missing a lead-gen template-step catalog.`
    )
  } else {
    requireCatalogReadiness(issues, index, templateStepCatalog.id, context)
    requireScopedBinding(issues, catalogs, 'usesCatalogs', templateStepCatalog.id, context)
  }

  const leadGenStageCatalog = requireCatalogReadiness(issues, index, LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID, context)
  return leadGenStageCatalog
}

function requireCrmInterfaceReadiness(
  issues: SystemInterfaceReadinessIssue[],
  index: BusinessOntologyValidationIndexBase,
  resources: ResourceEntry[],
  context: { systemPath: string; interfaceKey: string; allowForeignOwner?: boolean }
): OntologyCatalogType | undefined {
  const catalogs = resourceBindingIds(resources, 'usesCatalogs')
  const crmPipelineCatalog = requireCatalogReadiness(issues, index, CRM_PIPELINE_CATALOG_ONTOLOGY_ID, context)
  requireScopedBinding(issues, catalogs, 'usesCatalogs', CRM_PIPELINE_CATALOG_ONTOLOGY_ID, context)
  return crmPipelineCatalog
}

function findSystemInterfaceGrant(
  model: OrganizationModel,
  consumer: { systemPath: string; interfaceKey: string },
  provider: { systemPath: string; interfaceKey: string }
): OmTopologyRelationship | undefined {
  return Object.values(model.topology?.relationships ?? {}).find((relationship) => {
    const grant = relationship.metadata?.['systemInterfaceGrant']
    if (!isPlainRecord(grant) || !isPlainRecord(grant.consumer) || !isPlainRecord(grant.provider)) return false
    return (
      grant.consumer.systemPath === consumer.systemPath &&
      grant.consumer.interfaceKey === consumer.interfaceKey &&
      grant.provider.systemPath === provider.systemPath &&
      grant.provider.interfaceKey === provider.interfaceKey
    )
  })
}

function requireHandoffBridgeReadiness(
  issues: SystemInterfaceReadinessIssue[],
  model: OrganizationModel,
  context: { systemPath: string; interfaceKey: string }
): OmTopologyRelationship | undefined {
  const crmResult = computeInterfaceReadiness(model, CRM_API_INTERFACE)
  if (!crmResult.ready) {
    for (const issue of crmResult.issues) {
      addReadinessIssue(
        issues,
        'SYSTEM_BRIDGE_NOT_READY',
        issue.code,
        `Provider interface ${formatInterfaceIdentity(CRM_API_INTERFACE.systemPath, CRM_API_INTERFACE.interfaceKey)} is not ready: ${issue.message}`,
        { path: issue.path, ref: issue.ref }
      )
    }
  }

  const bridgeGrant = findSystemInterfaceGrant(model, context, CRM_API_INTERFACE)
  if (bridgeGrant === undefined) {
    addReadinessIssue(
      issues,
      'SYSTEM_BRIDGE_NOT_READY',
      'missing-topology-grant',
      `System Interface "${formatInterfaceIdentity(context.systemPath, context.interfaceKey)}" requires a scoped topology grant to "${formatInterfaceIdentity(CRM_API_INTERFACE.systemPath, CRM_API_INTERFACE.interfaceKey)}".`
    )
  }

  return bridgeGrant
}

function getLeadGenCrmHandoffResourceIds(model: OrganizationModel): string[] {
  return Object.values(model.resources ?? {})
    .filter(
      (resource) =>
        resource.systemPath === LEAD_GEN_CRM_HANDOFF_INTERFACE.systemPath &&
        resource.ontology?.usesCatalogs?.includes(CRM_PIPELINE_CATALOG_ONTOLOGY_ID) === true
    )
    .map((resource) => resource.id)
}

function getSystemInterfaceReadinessMarker(
  model: OrganizationModel,
  request: SystemInterfaceReadinessRequest
): { lifecycle: string; readinessProfile?: string; resourceIds?: string[] } | undefined {
  const system = getSystem(model, request.systemPath)
  if (system === undefined) return undefined

  if (request.interfaceKey === LEAD_GEN_API_INTERFACE.interfaceKey) {
    return system.apiInterface
  }

  if (
    request.systemPath === LEAD_GEN_CRM_HANDOFF_INTERFACE.systemPath &&
    request.interfaceKey === LEAD_GEN_CRM_HANDOFF_INTERFACE.interfaceKey
  ) {
    return {
      lifecycle: 'active',
      readinessProfile: LEAD_GEN_CRM_HANDOFF_INTERFACE.readinessProfile,
      resourceIds: getLeadGenCrmHandoffResourceIds(model)
    }
  }

  return undefined
}

function mergeLeadGenDerivedCatalogs(model: OrganizationModel): OrganizationModel {
  const baseCatalogTypes = model.ontology?.catalogTypes ?? {}
  const derivedCatalogTypes: Record<OntologyId, OntologyCatalogType> = {}

  // Lead-gen validators consume a normalized stage catalog derived from the
  // owning system's company/contact stage catalogs. CRM catalogs are never injected here.
  if (baseCatalogTypes[LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID] === undefined) {
    derivedCatalogTypes[LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID] = createLeadGenStageCatalog(model)
  }

  if (Object.keys(derivedCatalogTypes).length === 0) return model

  return {
    ...model,
    ontology: {
      ...(model.ontology ?? {}),
      catalogTypes: {
        ...baseCatalogTypes,
        ...derivedCatalogTypes
      }
    } satisfies OntologyScope
  }
}

function compileBusinessOntology(model: OrganizationModel, contractId: string): BusinessOntologyValidationIndexBase {
  const compilation = compileOrganizationOntology(mergeLeadGenDerivedCatalogs(model))
  if (compilation.diagnostics.length > 0) {
    const summary = compilation.diagnostics.map((diagnostic) => diagnostic.message).join('; ')
    throw new Error(`${contractId} ontology validation index failed to compile: ${summary}`)
  }

  return {
    ontology: compilation.ontology,
    actionTypesByLegacyId: indexActionTypesByLegacyId(compilation.ontology.actionTypes)
  }
}

function tryCompileBusinessOntology(
  model: OrganizationModel,
  readinessProfile: string,
  issues: SystemInterfaceReadinessIssue[]
): BusinessOntologyValidationIndexBase | undefined {
  try {
    return compileBusinessOntology(model, readinessProfile)
  } catch (error) {
    addReadinessIssue(
      issues,
      'SYSTEM_INTERFACE_INVALID',
      'ontology-compile-failed',
      error instanceof Error ? error.message : String(error)
    )
    return undefined
  }
}

export function computeInterfaceReadiness(
  model: OrganizationModel,
  request: SystemInterfaceReadinessRequest
): SystemInterfaceReadinessResult {
  const issues: SystemInterfaceReadinessIssue[] = []
  const system = getSystem(model, request.systemPath)
  const systemInterface = getSystemInterfaceReadinessMarker(model, request)
  const readinessProfile =
    systemInterface === undefined
      ? undefined
      : profileForInterface(request.systemPath, request.interfaceKey, systemInterface.readinessProfile)
  const scopedResourceIds = systemInterface?.resourceIds ?? []

  if (system === undefined) {
    addReadinessIssue(
      issues,
      'SYSTEM_INTERFACE_MISSING',
      'missing-system',
      `System "${request.systemPath}" is missing.`,
      { ref: request.systemPath }
    )
    return {
      ready: false,
      systemPath: request.systemPath,
      interfaceKey: request.interfaceKey,
      scopedResourceIds,
      issues
    }
  }

  if (systemInterface === undefined) {
    addReadinessIssue(
      issues,
      'SYSTEM_INTERFACE_MISSING',
      'missing-interface',
      `System "${request.systemPath}" does not declare interface "${request.interfaceKey}".`,
      { path: readinessMarkerPath(request) }
    )
    return {
      ready: false,
      systemPath: request.systemPath,
      interfaceKey: request.interfaceKey,
      scopedResourceIds,
      issues
    }
  }

  if (systemInterface.lifecycle !== 'active') {
    addReadinessIssue(
      issues,
      'SYSTEM_INTERFACE_DISABLED',
      'inactive-interface',
      `System Interface "${formatInterfaceIdentity(request.systemPath, request.interfaceKey)}" lifecycle is "${systemInterface.lifecycle}".`,
      { path: `${readinessMarkerPath(request)}.lifecycle` }
    )
  }

  const supportedProfile =
    readinessProfile !== undefined &&
    SYSTEM_INTERFACE_PROFILES.some((profile) => profile.readinessProfile === readinessProfile)

  if (!supportedProfile) {
    addReadinessIssue(
      issues,
      'SYSTEM_INTERFACE_INVALID',
      'unknown-readiness-profile',
      `System Interface "${formatInterfaceIdentity(request.systemPath, request.interfaceKey)}" references unknown readiness profile "${readinessProfile}". Supported profiles: ${formatSupportedReadinessProfiles()}. Custom Systems should not declare apiInterface; route custom behavior through workflows/operations plus ontology, resources, and topology.`,
      { path: `${readinessMarkerPath(request)}.readinessProfile`, ref: readinessProfile }
    )
    return {
      ready: false,
      systemPath: request.systemPath,
      interfaceKey: request.interfaceKey,
      readinessProfile,
      scopedResourceIds,
      issues
    }
  }

  if (scopedResourceIds.length === 0) {
    addReadinessIssue(
      issues,
      'SYSTEM_INTERFACE_NOT_READY',
      'missing-scoped-resources',
      `System Interface "${formatInterfaceIdentity(request.systemPath, request.interfaceKey)}" must scope at least one active resource.`,
      { path: `${readinessMarkerPath(request)}.resourceIds` }
    )
  }

  const checkedReadinessProfile = readinessProfile
  if (checkedReadinessProfile === undefined) {
    throw new Error('Supported readiness profile unexpectedly resolved to undefined')
  }

  const resources = getActiveScopedResources(model, scopedResourceIds, issues, request)
  const index = tryCompileBusinessOntology(model, checkedReadinessProfile, issues)

  if (index !== undefined) {
    if (checkedReadinessProfile === LEAD_GEN_API_INTERFACE.readinessProfile) {
      requireLeadGenInterfaceReadiness(issues, index, resources, request)
    } else if (checkedReadinessProfile === CRM_API_INTERFACE.readinessProfile) {
      requireCrmInterfaceReadiness(issues, index, resources, request)
    } else if (checkedReadinessProfile === LEAD_GEN_CRM_HANDOFF_INTERFACE.readinessProfile) {
      requireLeadGenInterfaceReadiness(issues, index, resources, request)
      requireCrmInterfaceReadiness(issues, index, resources, { ...request, allowForeignOwner: true })
      requireHandoffBridgeReadiness(issues, model, request)
    }
  }

  return {
    ready: issues.length === 0,
    systemPath: request.systemPath,
    interfaceKey: request.interfaceKey,
    readinessProfile,
    scopedResourceIds,
    issues
  }
}

function requireObjectType(index: BusinessOntologyValidationIndexBase, objectId: OntologyId, contractId: string): void {
  if (index.ontology.objectTypes[objectId] === undefined) {
    throw new Error(`${contractId} is missing required object type: ${objectId}`)
  }
}

function requireCatalog(
  index: BusinessOntologyValidationIndexBase,
  catalogId: OntologyId,
  contractId: string
): OntologyCatalogType {
  const catalog = index.ontology.catalogTypes[catalogId]
  if (catalog === undefined) {
    throw new Error(`${contractId} is missing required catalog: ${catalogId}`)
  }
  return catalog
}

function requireCatalogEntries(catalog: OntologyCatalogType, contractId: string): void {
  if (Object.keys(getCatalogEntries(catalog)).length === 0) {
    throw new Error(`${contractId} requires catalog entries for ${catalog.id}`)
  }
}

function findLeadGenTemplateStepCatalog(index: BusinessOntologyValidationIndexBase): OntologyCatalogType | undefined {
  return Object.values(index.ontology.catalogTypes).find(
    (catalog) =>
      catalog.ownerSystemId === 'sales.lead-gen' &&
      catalog.kind === 'template-step' &&
      catalog.appliesTo === LEAD_GEN_LIST_OBJECT_ONTOLOGY_ID
  )
}

function requireLeadGenApiReadiness(index: BusinessOntologyValidationIndexBase): OntologyCatalogType {
  requireObjectType(index, LEAD_GEN_LIST_OBJECT_ONTOLOGY_ID, LEAD_GEN_API_READINESS_LABEL)
  requireObjectType(index, LEAD_GEN_COMPANY_OBJECT_ONTOLOGY_ID, LEAD_GEN_API_READINESS_LABEL)
  requireObjectType(index, LEAD_GEN_CONTACT_OBJECT_ONTOLOGY_ID, LEAD_GEN_API_READINESS_LABEL)

  requireCatalogEntries(
    requireCatalog(index, LEAD_GEN_BUILD_TEMPLATE_CATALOG_ONTOLOGY_ID, LEAD_GEN_API_READINESS_LABEL),
    LEAD_GEN_API_READINESS_LABEL
  )
  requireCatalogEntries(
    requireCatalog(index, LEAD_GEN_COMPANY_STAGE_CATALOG_ONTOLOGY_ID, LEAD_GEN_API_READINESS_LABEL),
    LEAD_GEN_API_READINESS_LABEL
  )
  requireCatalogEntries(
    requireCatalog(index, LEAD_GEN_CONTACT_STAGE_CATALOG_ONTOLOGY_ID, LEAD_GEN_API_READINESS_LABEL),
    LEAD_GEN_API_READINESS_LABEL
  )

  const templateStepCatalog = findLeadGenTemplateStepCatalog(index)
  if (templateStepCatalog === undefined) {
    throw new Error(`${LEAD_GEN_API_READINESS_LABEL} is missing a lead-gen template-step catalog`)
  }
  requireCatalogEntries(templateStepCatalog, LEAD_GEN_API_READINESS_LABEL)

  const leadGenStageCatalog = requireCatalog(index, LEAD_GEN_STAGE_CATALOG_ONTOLOGY_ID, LEAD_GEN_API_READINESS_LABEL)
  requireCatalogEntries(leadGenStageCatalog, LEAD_GEN_API_READINESS_LABEL)
  return leadGenStageCatalog
}

function requireCrmApiReadiness(index: BusinessOntologyValidationIndexBase): OntologyCatalogType {
  const crmPipelineCatalog = requireCatalog(index, CRM_PIPELINE_CATALOG_ONTOLOGY_ID, CRM_API_READINESS_LABEL)
  requireCatalogEntries(crmPipelineCatalog, CRM_API_READINESS_LABEL)
  return crmPipelineCatalog
}

export function compileLeadGenApiOntologyValidationIndex(model: OrganizationModel): LeadGenApiOntologyValidationIndex {
  throwIfInterfaceNotReady(computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE))
  const index = compileBusinessOntology(model, LEAD_GEN_API_READINESS_LABEL)
  return {
    ...index,
    leadGenStageCatalog: requireLeadGenApiReadiness(index)
  }
}

export function compileCrmApiOntologyValidationIndex(model: OrganizationModel): CrmApiOntologyValidationIndex {
  throwIfInterfaceNotReady(computeInterfaceReadiness(model, CRM_API_INTERFACE))
  const index = compileBusinessOntology(model, CRM_API_READINESS_LABEL)
  return {
    ...index,
    crmPipelineCatalog: requireCrmApiReadiness(index)
  }
}

export function compileLeadGenCrmHandoffOntologyValidationIndex(
  model: OrganizationModel
): LeadGenCrmHandoffOntologyValidationIndex {
  const readiness = computeInterfaceReadiness(model, LEAD_GEN_CRM_HANDOFF_INTERFACE)
  throwIfInterfaceNotReady(readiness)
  const index = compileBusinessOntology(model, LEAD_GEN_CRM_HANDOFF_READINESS_LABEL)
  return {
    ...index,
    leadGenStageCatalog: requireLeadGenApiReadiness(index),
    crmPipelineCatalog: requireCrmApiReadiness(index),
    bridgeGrant: findSystemInterfaceGrant(model, LEAD_GEN_CRM_HANDOFF_INTERFACE, CRM_API_INTERFACE)
  }
}

/**
 * Legacy mixed acquisition index.
 *
 * Kept for existing API consumers until Wave 3 wires explicit route/service
 * contract requirements. New base lead-gen paths should call
 * `compileLeadGenApiOntologyValidationIndex()`; CRM-aware paths should call the
 * CRM or bridge helpers above.
 */
export function compileBusinessOntologyValidationIndex(model: OrganizationModel): BusinessOntologyValidationIndex {
  throwIfInterfaceNotReady(computeInterfaceReadiness(model, LEAD_GEN_API_INTERFACE))
  throwIfInterfaceNotReady(computeInterfaceReadiness(model, CRM_API_INTERFACE))
  const index = compileBusinessOntology(model, 'legacy acquisition business ontology')
  return {
    ...index,
    leadGenStageCatalog: requireLeadGenApiReadiness(index),
    crmPipelineCatalog: requireCrmApiReadiness(index)
  }
}

function indexActionTypesByLegacyId(
  actionTypes: Record<OntologyId, OntologyActionType>
): Record<string, OntologyActionType> {
  const byLegacyId: Record<string, OntologyActionType> = {}
  for (const actionType of Object.values(actionTypes)) {
    const legacyActionId = actionType['legacyActionId']
    if (typeof legacyActionId === 'string') {
      byLegacyId[legacyActionId] = actionType
    }
  }
  return byLegacyId
}

function getCatalogEntries(catalog: OntologyCatalogType): Record<string, unknown> {
  return isPlainRecord(catalog.entries) ? catalog.entries : {}
}

function asStatefulStateDefinition(value: unknown): StatefulStateDefinition | undefined {
  if (!isPlainRecord(value) || typeof value.stateKey !== 'string' || typeof value.label !== 'string') {
    return undefined
  }

  return {
    stateKey: value.stateKey,
    label: value.label
  }
}

function asLeadGenStageEntry(key: string, value: unknown): LeadGenStageCatalogEntry {
  const record = isPlainRecord(value) ? value : {}
  const entity = record.entity === 'contact' ? 'contact' : 'company'
  const additionalEntities = Array.isArray(record.additionalEntities)
    ? record.additionalEntities.filter(
        (item): item is 'company' | 'contact' => item === 'company' || item === 'contact'
      )
    : undefined
  const recordEntity =
    record.recordEntity === 'company' || record.recordEntity === 'contact' ? record.recordEntity : undefined
  const recordStageKey = typeof record.recordStageKey === 'string' ? record.recordStageKey : undefined

  return {
    key: typeof record.key === 'string' ? record.key : key,
    label: typeof record.label === 'string' ? record.label : key,
    description: typeof record.description === 'string' ? record.description : '',
    order: typeof record.order === 'number' ? record.order : 0,
    entity,
    ...(additionalEntities !== undefined ? { additionalEntities } : {}),
    ...(recordEntity !== undefined ? { recordEntity } : {}),
    ...(recordStageKey !== undefined ? { recordStageKey } : {})
  }
}

/**
 * Returns the valid states for a given CRM stage key.
 * Resolves against the caller-supplied ontology validation index so generic
 * core does not materialize an Elevasis CRM pipeline singleton.
 */
export function getCrmStatesForStage(
  index: CrmApiOntologyValidationIndex,
  stageKey: string
): StatefulStateDefinition[] {
  const stage = getCatalogEntries(index.crmPipelineCatalog)[stageKey]
  const states = isPlainRecord(stage) && Array.isArray(stage.states) ? stage.states : []
  return states.flatMap((state) => {
    const parsed = asStatefulStateDefinition(state)
    return parsed ? [parsed] : []
  })
}

/**
 * Lead-gen stage validators bound to a specific resolved validation index.
 *
 * The lead-gen stage catalog is tenant-model-dependent (it derives from the
 * organization model's `sales.lead-gen` stage records). Hosts that own a
 * populated tenant model (e.g. `apps/api` with the Elevasis canonical model)
 * MUST build their own index via `compileLeadGenApiOntologyValidationIndex(model)`
 * and call `createLeadGenStageValidators(index)`. The core package stays generic;
 * it must never import a tenant model.
 */
export type LeadGenStageValidators = {
  getLeadGenStageCatalog(): Record<string, LeadGenStageCatalogEntry>
  getLeadGenStageEntry(stageKey: string): LeadGenStageCatalogEntry | undefined
  isLeadGenStageKey(stageKey: string): boolean
  isLeadGenStageValidForEntity(stageKey: string, entity: 'company' | 'contact'): boolean
  isLeadGenRecordStageValidForEntity(stageKey: string, entity: 'company' | 'contact'): boolean
  resolveLeadGenRecordStageKey(stageKey: string, entity: 'company' | 'contact'): string
}

export function createLeadGenStageValidators(index: LeadGenApiOntologyValidationIndex): LeadGenStageValidators {
  const getCatalog = (): Record<string, LeadGenStageCatalogEntry> =>
    Object.fromEntries(
      Object.entries(getCatalogEntries(index.leadGenStageCatalog)).map(([key, value]) => [
        key,
        asLeadGenStageEntry(key, value)
      ])
    )
  const getEntry = (stageKey: string): LeadGenStageCatalogEntry | undefined => getCatalog()[stageKey]

  return {
    getLeadGenStageCatalog: getCatalog,
    getLeadGenStageEntry: getEntry,
    isLeadGenStageKey: (stageKey) => getEntry(stageKey) !== undefined,
    isLeadGenStageValidForEntity: (stageKey, entity) => {
      const stage = getEntry(stageKey)
      return stage !== undefined && (stage.entity === entity || stage.additionalEntities?.includes(entity) === true)
    },
    isLeadGenRecordStageValidForEntity: (stageKey, entity) => {
      const stage = getEntry(stageKey)
      return (
        stage !== undefined &&
        (stage.entity === entity ||
          stage.additionalEntities?.includes(entity) === true ||
          stage.recordEntity === entity)
      )
    },
    resolveLeadGenRecordStageKey: (stageKey, entity) => {
      const stage = getEntry(stageKey)
      return stage?.recordEntity === entity && stage.recordStageKey ? stage.recordStageKey : stageKey
    }
  }
}
