import { z } from 'zod'
import type { SidebarNode } from './domains/navigation'
import { ContractRefSchema } from './domains/resources'
import type { SystemEntry } from './domains/systems'
import type { OmTopologyNodeRef } from './domains/topology'
import { buildOmCompilationContext, knowledgeTargetExists, ONTOLOGY_REFERENCE_KEY_KINDS } from './cross-ref'
import { listResolvedOntologyRecords, type OntologyKind } from './ontology'
import type { OrganizationModel } from './types'

function addIssue(ctx: z.RefinementCtx, path: Array<string | number>, message: string): void {
  ctx.addIssue({
    code: z.ZodIssueCode.custom,
    path,
    message
  })
}

function isLifecycleEnabled(lifecycle: string | undefined, enabled: boolean | undefined): boolean {
  if (enabled === false) return false
  return lifecycle !== 'deprecated' && lifecycle !== 'archived'
}

function defaultSystemPathFor(id: string): string {
  return `/${id.replaceAll('.', '/')}`
}

function asRoleHolderArray(heldBy: NonNullable<OrganizationModel['roles'][string]['heldBy']>) {
  return Array.isArray(heldBy) ? heldBy : [heldBy]
}

function isKnowledgeKindCompatibleWithTarget(knowledgeKind: string, targetKind: string): boolean {
  if (knowledgeKind === 'reference') return true
  if (knowledgeKind === 'playbook') {
    return ['system', 'client', 'resource', 'stage', 'action', 'ontology'].includes(targetKind)
  }
  if (knowledgeKind === 'strategy') {
    return ['system', 'client', 'goal', 'offering', 'customer-segment', 'ontology'].includes(targetKind)
  }
  return false
}

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

export function refineOrganizationModel(model: OrganizationModel, ctx: z.RefinementCtx): void {
  // Collect ALL system entries recursively — top-level systems plus any nested subsystems.
  // Wave 2 canonical OM authors nested subsystems (e.g. sys → subsystems → 'lead-gen' with id
  // 'sys.lead-gen'). Resource systemPath cross-refs must resolve against the full flattened set.
  type SystemWithPath = { path: string; schemaPath: Array<string | number>; system: SystemEntry }

  function collectAllSystems(
    systems: Record<string, SystemEntry>,
    prefix = '',
    schemaPath: Array<string | number> = ['systems']
  ): SystemWithPath[] {
    const result: SystemWithPath[] = []
    for (const [key, system] of Object.entries(systems)) {
      const path = prefix ? `${prefix}.${key}` : key
      const currentSchemaPath = [...schemaPath, key]
      result.push({ path, schemaPath: currentSchemaPath, system })
      const childSystems = system.systems ?? system.subsystems
      if (childSystems !== undefined) {
        result.push(
          ...collectAllSystems(childSystems, path, [
            ...currentSchemaPath,
            system.systems !== undefined ? 'systems' : 'subsystems'
          ])
        )
      }
    }
    return result
  }

  const allSystems = collectAllSystems(model.systems)
  const systemsById = new Map<string, SystemEntry>()
  for (const { path, system } of allSystems) {
    systemsById.set(path, system)
    systemsById.set(system.id, system)
  }

  const systemIdsByEffectivePath = new Map<string, string>()
  allSystems.forEach(({ path, schemaPath, system }) => {
    if (system.parentSystemId !== undefined && !systemsById.has(system.parentSystemId)) {
      addIssue(
        ctx,
        [...schemaPath, 'parentSystemId'],
        `System "${system.id}" references unknown parent "${system.parentSystemId}"`
      )
    }

    const hasChildren =
      Object.keys(system.systems ?? system.subsystems ?? {}).length > 0 ||
      allSystems.some(
        (candidate) => candidate.path.startsWith(`${path}.`) && !candidate.path.slice(path.length + 1).includes('.')
      )
    const contributesRoutePath = system.ui?.path !== undefined || system.path !== undefined || !hasChildren
    if (contributesRoutePath) {
      const effectivePath = system.ui?.path ?? system.path ?? defaultSystemPathFor(path)
      const existingSystemId = systemIdsByEffectivePath.get(effectivePath)
      if (existingSystemId !== undefined) {
        addIssue(
          ctx,
          [...schemaPath, system.ui?.path !== undefined ? 'ui' : 'path'],
          `System "${path}" effective path "${effectivePath}" duplicates system "${existingSystemId}"`
        )
      } else {
        systemIdsByEffectivePath.set(effectivePath, path)
      }
    }

    if (hasChildren && isLifecycleEnabled(system.lifecycle, system.enabled)) {
      const hasEnabledDescendant =
        Object.values(system.systems ?? system.subsystems ?? {}).some((candidate) =>
          isLifecycleEnabled(candidate.lifecycle, candidate.enabled)
        ) ||
        allSystems.some(
          (candidate) =>
            candidate.path.startsWith(`${path}.`) &&
            !candidate.path.slice(path.length + 1).includes('.') &&
            isLifecycleEnabled(candidate.system.lifecycle, candidate.system.enabled)
        )
      if (!hasEnabledDescendant) {
        addIssue(ctx, [...schemaPath, 'lifecycle'], `System "${path}" is active but has no active descendants`)
      }
    }
  })

  allSystems.forEach(({ schemaPath, system }) => {
    const visited = new Set<string>()
    let currentParentId = system.parentSystemId

    while (currentParentId !== undefined) {
      if (currentParentId === system.id || visited.has(currentParentId)) {
        addIssue(ctx, [...schemaPath, 'parentSystemId'], `System "${system.id}" has a parent cycle`)
        return
      }

      visited.add(currentParentId)
      currentParentId = systemsById.get(currentParentId)?.parentSystemId
    }
  })

  type CollectedSidebarSurface = {
    id: string
    node: Extract<SidebarNode, { type: 'surface' }>
    path: Array<string | number>
  }

  function normalizeRoutePath(path: string): string {
    return path.length > 1 ? path.replace(/\/+$/, '') : path
  }

  const sidebarNodeIds = new Map<string, Array<string | number>>()
  const sidebarSurfacePaths = new Map<string, string>()
  const sidebarSurfaces: CollectedSidebarSurface[] = []

  function collectSidebarNodes(nodes: Record<string, SidebarNode>, schemaPath: Array<string | number>): void {
    Object.entries(nodes).forEach(([nodeId, node]) => {
      const nodePath = [...schemaPath, nodeId]
      const existingNodePath = sidebarNodeIds.get(nodeId)
      if (existingNodePath !== undefined) {
        addIssue(ctx, nodePath, `Sidebar node id "${nodeId}" duplicates another sidebar node`)
      } else {
        sidebarNodeIds.set(nodeId, nodePath)
      }

      if (node.type === 'group') {
        collectSidebarNodes(node.children, [...nodePath, 'children'])
        return
      }

      sidebarSurfaces.push({ id: nodeId, node, path: nodePath })
      const normalizedPath = normalizeRoutePath(node.path)
      const existingSurfaceId = sidebarSurfacePaths.get(normalizedPath)
      if (existingSurfaceId !== undefined) {
        addIssue(
          ctx,
          [...nodePath, 'path'],
          `Sidebar surface path "${node.path}" duplicates surface "${existingSurfaceId}"`
        )
      } else {
        sidebarSurfacePaths.set(normalizedPath, nodeId)
      }

      node.targets?.systems?.forEach((systemId, systemIndex) => {
        if (!systemsById.has(systemId)) {
          addIssue(
            ctx,
            [...nodePath, 'targets', 'systems', systemIndex],
            `Sidebar surface "${nodeId}" references unknown system "${systemId}"`
          )
        }
      })
    })
  }

  collectSidebarNodes(model.navigation.sidebar.primary, ['navigation', 'sidebar', 'primary'])
  collectSidebarNodes(model.navigation.sidebar.bottom, ['navigation', 'sidebar', 'bottom'])

  // Offerings -> CustomerSegment cross-ref: targetSegmentIds must resolve
  const segmentsById = new Map(Object.entries(model.customers))
  Object.values(model.offerings).forEach((product) => {
    product.targetSegmentIds.forEach((segmentId, segmentIndex) => {
      if (!segmentsById.has(segmentId)) {
        addIssue(
          ctx,
          ['offerings', product.id, 'targetSegmentIds', segmentIndex],
          `Product "${product.id}" references unknown customer segment "${segmentId}"`
        )
      }
    })

    // Offerings -> System cross-ref: deliveryFeatureId must resolve (when present)
    if (product.deliveryFeatureId !== undefined && !systemsById.has(product.deliveryFeatureId)) {
      addIssue(
        ctx,
        ['offerings', product.id, 'deliveryFeatureId'],
        `Product "${product.id}" references unknown delivery system "${product.deliveryFeatureId}"`
      )
    }
  })

  // Goals -> period-range validation: periodEnd must be strictly after periodStart
  Object.values(model.goals).forEach((objective) => {
    if (objective.periodEnd <= objective.periodStart) {
      addIssue(
        ctx,
        ['goals', objective.id, 'periodEnd'],
        `Goal "${objective.id}" has periodEnd "${objective.periodEnd}" which must be strictly after periodStart "${objective.periodStart}"`
      )
    }
  })

  const goalsById = new Map(Object.entries(model.goals))
  // Phase 4: knowledge is now a flat Record<id, OrgKnowledgeNode> — no .nodes array
  const knowledgeById = new Map(Object.entries(model.knowledge))
  const actionsById = new Map(Object.entries(model.actions))
  const entitiesById = new Map(Object.entries(model.entities))
  const policiesById = new Map(Object.entries(model.policies))

  sidebarSurfaces.forEach(({ id, node, path }) => {
    node.targets?.entities?.forEach((entityId, entityIndex) => {
      if (!entitiesById.has(entityId)) {
        addIssue(
          ctx,
          [...path, 'targets', 'entities', entityIndex],
          `Sidebar surface "${id}" references unknown entity "${entityId}"`
        )
      }
    })

    node.targets?.actions?.forEach((actionId, actionIndex) => {
      if (!actionsById.has(actionId)) {
        addIssue(
          ctx,
          [...path, 'targets', 'actions', actionIndex],
          `Sidebar surface "${id}" references unknown action "${actionId}"`
        )
      }
    })
  })

  // An empty `systems` map is the documented "no tenant model yet" shell
  // sentinel (generic core ships empty systems; tenant/canonical models supply
  // their own). Validating entity ownership refs against an intentionally-empty
  // systems map is meaningless, so only enforce when systems are declared.
  Object.values(model.entities).forEach((entity) => {
    if (systemsById.size > 0 && !systemsById.has(entity.ownedBySystemId)) {
      addIssue(
        ctx,
        ['entities', entity.id, 'ownedBySystemId'],
        `Entity "${entity.id}" references unknown ownedBySystemId "${entity.ownedBySystemId}"`
      )
    }

    entity.links?.forEach((link, linkIndex) => {
      if (!entitiesById.has(link.toEntity)) {
        addIssue(
          ctx,
          ['entities', entity.id, 'links', linkIndex, 'toEntity'],
          `Entity "${entity.id}" links to unknown entity "${link.toEntity}"`
        )
      }
    })
  })

  // Roles -> reportsToId cross-ref: each reportsToId must resolve to another role in the same collection
  const rolesById = new Map(Object.entries(model.roles))
  Object.values(model.roles).forEach((role) => {
    if (role.reportsToId !== undefined && !rolesById.has(role.reportsToId)) {
      addIssue(
        ctx,
        ['roles', role.id, 'reportsToId'],
        `Role "${role.id}" references unknown reportsToId "${role.reportsToId}"`
      )
    }
  })

  Object.values(model.roles).forEach((role) => {
    const visited = new Set<string>()
    let currentReportsToId = role.reportsToId

    while (currentReportsToId !== undefined) {
      if (currentReportsToId === role.id || visited.has(currentReportsToId)) {
        addIssue(ctx, ['roles', role.id, 'reportsToId'], `Role "${role.id}" has a reportsToId cycle`)
        return
      }

      visited.add(currentReportsToId)
      currentReportsToId = rolesById.get(currentReportsToId)?.reportsToId
    }
  })

  Object.values(model.roles).forEach((role) => {
    role.responsibleFor?.forEach((systemId, systemIndex) => {
      if (!systemsById.has(systemId)) {
        addIssue(
          ctx,
          ['roles', role.id, 'responsibleFor', systemIndex],
          `Role "${role.id}" references unknown responsibleFor system "${systemId}"`
        )
      }
    })
  })

  allSystems.forEach(({ schemaPath, system }) => {
    if (system.responsibleRoleId !== undefined && !rolesById.has(system.responsibleRoleId)) {
      addIssue(
        ctx,
        [...schemaPath, 'responsibleRoleId'],
        `System "${system.id}" references unknown responsibleRoleId "${system.responsibleRoleId}"`
      )
    }

    system.governedByKnowledge?.forEach((nodeId, nodeIndex) => {
      if (!knowledgeById.has(nodeId)) {
        addIssue(
          ctx,
          [...schemaPath, 'governedByKnowledge', nodeIndex],
          `System "${system.id}" references unknown knowledge node "${nodeId}"`
        )
      }
    })

    system.drivesGoals?.forEach((goalId, goalIndex) => {
      if (!goalsById.has(goalId)) {
        addIssue(
          ctx,
          [...schemaPath, 'drivesGoals', goalIndex],
          `System "${system.id}" references unknown goal "${goalId}"`
        )
      }
    })

    system.actions?.forEach((actionRef, actionIndex) => {
      if (!actionsById.has(actionRef.actionId)) {
        addIssue(
          ctx,
          [...schemaPath, 'actions', actionIndex, 'actionId'],
          `System "${system.id}" references unknown action "${actionRef.actionId}"`
        )
      }
    })

    system.policies?.forEach((policyId, policyIndex) => {
      if (!policiesById.has(policyId)) {
        addIssue(
          ctx,
          [...schemaPath, 'policies', policyIndex],
          `System "${system.id}" references unknown policy "${policyId}"`
        )
      }
    })

  })

  Object.values(model.actions).forEach((action) => {
    action.affects?.forEach((entityId, entityIndex) => {
      if (!entitiesById.has(entityId)) {
        addIssue(
          ctx,
          ['actions', action.id, 'affects', entityIndex],
          `Action "${action.id}" affects unknown entity "${entityId}"`
        )
      }
    })
  })

  // Phase 4: sales / prospecting / projects compound-domain entity cross-ref checks removed.
  // Those entity bindings now live in System.ontology catalog scopes.

  const resourcesById = new Map(Object.entries(model.resources))
  sidebarSurfaces.forEach(({ id, node, path }) => {
    node.targets?.resources?.forEach((resourceId, resourceIndex) => {
      if (!resourcesById.has(resourceId)) {
        addIssue(
          ctx,
          [...path, 'targets', 'resources', resourceIndex],
          `Sidebar surface "${id}" references unknown resource "${resourceId}"`
        )
      }
    })
  })
  // Shared cross-reference index — single source of truth for (kind, id) resolution.
  const { crossRefIndex: idx, ontologyCompilation } = buildOmCompilationContext(model)
  const { ontologyIndexByKind, ontologyIds } = idx

  function topologyTargetExists(ref: OmTopologyNodeRef): boolean {
    if (ref.kind === 'system') return systemsById.has(ref.id)
    if (ref.kind === 'resource') return resourcesById.has(ref.id)
    if (ref.kind === 'ontology') return ontologyIds.has(ref.id)
    if (ref.kind === 'policy') return policiesById.has(ref.id)
    if (ref.kind === 'role') return rolesById.has(ref.id)

    // Trigger, human checkpoint, and external resource refs are projected
    // topology nodes during the bridge period; their owning runtime indexes are
    // validated by deployment projection in later waves.
    return true
  }

  Object.entries(model.topology.relationships).forEach(([relationshipId, relationship]) => {
    ;(['from', 'to'] as const).forEach((side) => {
      const ref = relationship[side]
      if (topologyTargetExists(ref)) return

      addIssue(
        ctx,
        ['topology', 'relationships', relationshipId, side],
        `Topology relationship "${relationshipId}" ${side} references unknown ${ref.kind} "${ref.id}"`
      )
    })
  })

  function validateKnownOntologyReferences(
    ownerId: string,
    value: unknown,
    path: Array<string | number>,
    seen = new WeakSet<object>()
  ): void {
    if (Array.isArray(value)) {
      value.forEach((entry, index) => validateKnownOntologyReferences(ownerId, entry, [...path, index], seen))
      return
    }

    if (!isRecord(value)) return
    if (seen.has(value)) return
    seen.add(value)

    Object.entries(value).forEach(([key, entry]) => {
      const expectedKind = ONTOLOGY_REFERENCE_KEY_KINDS[key]
      if (expectedKind !== undefined) {
        if (typeof entry !== 'string') {
          addIssue(ctx, [...path, key], `Ontology record "${ownerId}" ${key} must be an ontology ID string`)
        } else if (ontologyIndexByKind[expectedKind][entry] === undefined) {
          addIssue(
            ctx,
            [...path, key],
            `Ontology record "${ownerId}" ${key} references unknown ${expectedKind} ontology ID "${entry}"`
          )
        }
      }

      validateKnownOntologyReferences(ownerId, entry, [...path, key], seen)
    })
  }

  for (const { id, record } of listResolvedOntologyRecords(ontologyCompilation.ontology)) {
    validateKnownOntologyReferences(id, record, record.origin.path)
  }

  Object.values(model.policies).forEach((policy) => {
    policy.appliesTo.systemIds.forEach((systemId, systemIndex) => {
      if (!systemsById.has(systemId)) {
        addIssue(
          ctx,
          ['policies', policy.id, 'appliesTo', 'systemIds', systemIndex],
          `Policy "${policy.id}" applies to unknown system "${systemId}"`
        )
      }
    })

    policy.appliesTo.actionIds.forEach((actionId, actionIndex) => {
      if (!actionsById.has(actionId)) {
        addIssue(
          ctx,
          ['policies', policy.id, 'appliesTo', 'actionIds', actionIndex],
          `Policy "${policy.id}" applies to unknown action "${actionId}"`
        )
      }
    })

    policy.actions.forEach((action, actionIndex) => {
      if (action.kind === 'invoke-action' && !actionsById.has(action.actionId)) {
        addIssue(
          ctx,
          ['policies', policy.id, 'actions', actionIndex, 'actionId'],
          `Policy "${policy.id}" invokes unknown action "${action.actionId}"`
        )
      }
      if (
        (action.kind === 'notify-role' || action.kind === 'require-approval') &&
        action.roleId !== undefined &&
        !rolesById.has(action.roleId)
      ) {
        addIssue(
          ctx,
          ['policies', policy.id, 'actions', actionIndex, 'roleId'],
          `Policy "${policy.id}" references unknown role "${action.roleId}"`
        )
      }
    })

    if (policy.trigger.kind === 'action-invocation' && !actionsById.has(policy.trigger.actionId)) {
      addIssue(
        ctx,
        ['policies', policy.id, 'trigger', 'actionId'],
        `Policy "${policy.id}" references unknown trigger action "${policy.trigger.actionId}"`
      )
    }
  })

  // Phase 4: model.knowledge is now Record<id, OrgKnowledgeNode> — iterate Object.values
  Object.entries(model.knowledge).forEach(([nodeId, node]) => {
    node.links.forEach((link, linkIndex) => {
      if (!knowledgeTargetExists(idx, link.target.kind, link.target.id)) {
        addIssue(
          ctx,
          ['knowledge', nodeId, 'links', linkIndex, 'target'],
          `Knowledge node "${node.id}" references unknown ${link.target.kind} target "${link.target.id}"`
        )
      }

      if (!isKnowledgeKindCompatibleWithTarget(node.kind, link.target.kind)) {
        addIssue(
          ctx,
          ['knowledge', nodeId, 'links', linkIndex, 'target', 'kind'],
          `Knowledge node "${node.id}" kind "${node.kind}" cannot govern ${link.target.kind} targets`
        )
      }

      // `governedByKnowledge` is validated one-way on target nodes above. Knowledge
      // links may be authored first and remain valid as forward references.
    })
  })

  Object.values(model.resources).forEach((resource) => {
    if (!systemsById.has(resource.systemPath)) {
      addIssue(
        ctx,
        ['resources', resource.id, 'systemPath'],
        `Resource "${resource.id}" references unknown system path "${resource.systemPath}"`
      )
    }

    if (resource.ownerRoleId !== undefined && !rolesById.has(resource.ownerRoleId)) {
      addIssue(
        ctx,
        ['resources', resource.id, 'ownerRoleId'],
        `Resource "${resource.id}" references unknown ownerRoleId "${resource.ownerRoleId}"`
      )
    }

    if (resource.kind === 'agent' && resource.actsAsRoleId !== undefined && !rolesById.has(resource.actsAsRoleId)) {
      addIssue(
        ctx,
        ['resources', resource.id, 'actsAsRoleId'],
        `Agent resource "${resource.id}" references unknown actsAsRoleId "${resource.actsAsRoleId}"`
      )
    }
  })

  allSystems.forEach(({ path: systemPath, schemaPath, system }) => {
    system.apiInterface?.resourceIds?.forEach((resourceId, resourceIndex) => {
      const resource = resourcesById.get(resourceId)
      if (resource === undefined) {
        addIssue(
          ctx,
          [...schemaPath, 'apiInterface', 'resourceIds', resourceIndex],
          `System Interface "${systemPath}/api" scopes unknown resource "${resourceId}"`
        )
        return
      }

      if (resource.systemPath !== systemPath) {
        addIssue(
          ctx,
          [...schemaPath, 'apiInterface', 'resourceIds', resourceIndex],
          `System Interface "${systemPath}/api" scopes resource "${resourceId}" from system "${resource.systemPath}"`
        )
      }
    })
  })

  function validateResourceOntologyBinding(
    resourceId: string,
    bindingKey: 'actions' | 'primaryAction' | 'reads' | 'writes' | 'usesCatalogs' | 'emits',
    expectedKind: OntologyKind,
    ids: string[] | string | undefined
  ): void {
    const ontologyIds = ids === undefined ? [] : Array.isArray(ids) ? ids : [ids]

    ontologyIds.forEach((ontologyId, ontologyIndex) => {
      if (ontologyIndexByKind[expectedKind][ontologyId] === undefined) {
        addIssue(
          ctx,
          ['resources', resourceId, 'ontology', bindingKey, ...(Array.isArray(ids) ? [ontologyIndex] : [])],
          `Resource "${resourceId}" ontology binding "${bindingKey}" references unknown ${expectedKind} ontology ID "${ontologyId}"`
        )
      }
    })
  }

  Object.values(model.resources).forEach((resource) => {
    const binding = resource.ontology
    if (binding === undefined) return

    validateResourceOntologyBinding(resource.id, 'actions', 'action', binding.actions)
    validateResourceOntologyBinding(resource.id, 'primaryAction', 'action', binding.primaryAction)
    validateResourceOntologyBinding(resource.id, 'reads', 'object', binding.reads)
    validateResourceOntologyBinding(resource.id, 'writes', 'object', binding.writes)
    validateResourceOntologyBinding(resource.id, 'usesCatalogs', 'catalog', binding.usesCatalogs)
    validateResourceOntologyBinding(resource.id, 'emits', 'event', binding.emits)

    // Tier-1: validate contract ref SHAPE only — no module resolution (browser-safe).
    // Tier-2 intra-package resolution runs in om:verify (packages/cli/src/knowledge/verify.ts).
    if (binding.contract !== undefined) {
      const contractEntries = [
        ['input', binding.contract.input],
        ['output', binding.contract.output]
      ] as const
      for (const [side, ref] of contractEntries) {
        if (ref === undefined) continue
        const result = ContractRefSchema.safeParse(ref)
        if (!result.success) {
          addIssue(
            ctx,
            ['resources', resource.id, 'ontology', 'contract', side],
            `Resource "${resource.id}" contract.${side} "${ref}" is not a valid ContractRef (expected "package/subpath#ExportName")`
          )
        }
      }
    }
  })

  Object.values(model.roles).forEach((role) => {
    if (role.heldBy === undefined) return

    asRoleHolderArray(role.heldBy).forEach((holder, holderIndex) => {
      if (holder.kind !== 'agent') return

      const resource = resourcesById.get(holder.agentId)
      if (resource === undefined) {
        addIssue(
          ctx,
          ['roles', role.id, 'heldBy', Array.isArray(role.heldBy) ? holderIndex : 'agentId'],
          `Role "${role.id}" references unknown agent holder resource "${holder.agentId}"`
        )
        return
      }

      if (resource.kind !== 'agent') {
        addIssue(
          ctx,
          ['roles', role.id, 'heldBy', Array.isArray(role.heldBy) ? holderIndex : 'agentId'],
          `Role "${role.id}" agent holder "${holder.agentId}" must reference an agent resource`
        )
      }
    })
  })

  // Phase 4: model.knowledge is now Record<id, OrgKnowledgeNode>
  Object.entries(model.knowledge).forEach(([nodeId, node]) => {
    node.ownerIds.forEach((roleId, ownerIndex) => {
      if (!rolesById.has(roleId)) {
        addIssue(
          ctx,
          ['knowledge', nodeId, 'ownerIds', ownerIndex],
          `Knowledge node "${node.id}" references unknown owner role "${roleId}"`
        )
      }
    })
  })

  for (const diagnostic of ontologyCompilation.diagnostics) {
    addIssue(ctx, diagnostic.path, diagnostic.message)
  }
}
