import { describe, expect, it } from 'vitest'
import { buildOrganizationGraph } from '../graph/build'
import { OrganizationGraphEdgeKindSchema, OrganizationGraphNodeKindSchema } from '../graph/schema'
import { resolveOrganizationModel } from '../resolve'
import { resolveSystemConfig } from '../helpers'

const TEST_ACTION = {
  id: 'lead-gen.company.source',
  order: 10,
  label: 'Source companies',
  description: 'Import source companies from a list provider.',
  resourceId: 'lgn-import-workflow',
  invocations: [{ kind: 'api-endpoint' as const, method: 'POST' as const, path: '/api/prospecting/companies/source' }]
}

const TEST_SYSTEMS = {
  sales: {
    id: 'sales',
    order: 10,
    label: 'Sales',
    lifecycle: 'active' as const,
    systems: {
      'lead-gen': {
        id: 'sales.lead-gen',
        order: 20,
        label: 'Lead Gen',
        lifecycle: 'active' as const,
        actions: [{ actionId: TEST_ACTION.id, intent: 'exposes' as const }]
      },
      crm: {
        id: 'sales.crm',
        order: 30,
        label: 'CRM',
        lifecycle: 'active' as const
      }
    }
  },
  operations: {
    id: 'operations',
    order: 40,
    label: 'Operations',
    lifecycle: 'active' as const
  }
}

const TEST_ENTITIES = {
  'crm.deal': {
    id: 'crm.deal',
    order: 10,
    label: 'Deal',
    ownedBySystemId: 'sales.crm',
    links: [{ toEntity: 'crm.contact', kind: 'has-many' as const, label: 'contacts' }]
  },
  'crm.contact': {
    id: 'crm.contact',
    order: 20,
    label: 'CRM Contact',
    ownedBySystemId: 'sales.crm'
  }
}

const BASE_GRAPH_MODEL = {
  systems: TEST_SYSTEMS,
  actions: { [TEST_ACTION.id]: TEST_ACTION },
  entities: TEST_ENTITIES
}

describe('organization graph', () => {
  it('emits system nodes from the systems domain', () => {
    const graph = buildOrganizationGraph({ organizationModel: resolveOrganizationModel(BASE_GRAPH_MODEL) })

    expect(graph.nodes.find((node) => node.id === 'system:sales.crm')).toMatchObject({
      kind: 'system',
      sourceId: 'sales.crm',
      label: 'CRM'
    })
    expect(
      graph.edges.find((edge) => edge.sourceId === 'system:sales' && edge.targetId === 'system:sales.crm')
    ).toMatchObject({
      kind: 'contains'
    })
  })

  it('uses overridden system labels', () => {
    const model = resolveOrganizationModel({
      systems: { custom: { id: 'custom', order: 10, label: 'Custom Workspace', enabled: true, path: '/custom' } }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(graph.nodes.find((node) => node.id === 'system:custom')?.label).toBe('Custom Workspace')
  })

  it('emits system and role nodes from the organization model', () => {
    const model = resolveOrganizationModel({
      systems: {
        'sys.platform': {
          id: 'sys.platform',
          order: 10,
          label: 'Platform',
          description: 'Platform operations.',
          kind: 'platform',
          lifecycle: 'active'
        },
        'sys.platform.observability': {
          id: 'sys.platform.observability',
          order: 20,
          label: 'Observability',
          kind: 'diagnostic',
          parentSystemId: 'sys.platform',
          lifecycle: 'active'
        }
      },
      roles: {
        'role.platform-owner': {
          id: 'role.platform-owner',
          order: 10,
          title: 'Platform Owner',
          responsibilities: ['Own platform operations'],
          responsibleFor: ['sys.platform']
        },
        'role.observability-lead': {
          id: 'role.observability-lead',
          order: 20,
          title: 'Observability Lead',
          reportsToId: 'role.platform-owner',
          responsibleFor: ['sys.platform.observability']
        }
      }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(graph.nodes.find((node) => node.id === 'system:sys.platform')).toMatchObject({
      kind: 'system',
      sourceId: 'sys.platform',
      label: 'Platform'
    })
    expect(graph.nodes.find((node) => node.id === 'role:role.platform-owner')).toMatchObject({
      kind: 'role',
      sourceId: 'role.platform-owner',
      label: 'Platform Owner'
    })
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'contains' &&
          edge.sourceId === 'system:sys.platform' &&
          edge.targetId === 'system:sys.platform.observability'
      )
    ).toBeDefined()
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'references' &&
          edge.sourceId === 'role:role.observability-lead' &&
          edge.targetId === 'role:role.platform-owner'
      )
    ).toMatchObject({ label: 'reports to' })
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'governs' &&
          edge.sourceId === 'role:role.platform-owner' &&
          edge.targetId === 'system:sys.platform'
      )
    ).toMatchObject({ label: 'responsible for' })
  })

  it('keeps graph schema to emitted node and edge kinds', () => {
    expect(() => OrganizationGraphNodeKindSchema.parse('role')).not.toThrow()
    expect(() => OrganizationGraphNodeKindSchema.parse('system')).not.toThrow()
    expect(() => OrganizationGraphNodeKindSchema.parse('entity')).not.toThrow()
    expect(() => OrganizationGraphNodeKindSchema.parse('event')).not.toThrow()
    expect(() => OrganizationGraphNodeKindSchema.parse('policy')).not.toThrow()
    expect(() => OrganizationGraphEdgeKindSchema.parse('links')).not.toThrow()
    expect(() => OrganizationGraphEdgeKindSchema.parse('affects')).not.toThrow()
    expect(() => OrganizationGraphEdgeKindSchema.parse('emits')).not.toThrow()
    expect(() => OrganizationGraphEdgeKindSchema.parse('originates_from')).not.toThrow()
    expect(() => OrganizationGraphEdgeKindSchema.parse('triggers')).not.toThrow()
    expect(() => OrganizationGraphEdgeKindSchema.parse('approval')).not.toThrow()
    expect(() => OrganizationGraphNodeKindSchema.parse('surface')).not.toThrow()
    expect(() => OrganizationGraphNodeKindSchema.parse('customer-segment')).not.toThrow()
    expect(() => OrganizationGraphNodeKindSchema.parse('offering')).not.toThrow()
    expect(() => OrganizationGraphNodeKindSchema.parse('goal')).not.toThrow()
    expect(() => OrganizationGraphNodeKindSchema.parse('navigation-group')).not.toThrow()
    expect(() => OrganizationGraphNodeKindSchema.parse('ontology')).not.toThrow()
    expect(() => OrganizationGraphEdgeKindSchema.parse('actions')).not.toThrow()
    expect(() => OrganizationGraphEdgeKindSchema.parse('reads')).not.toThrow()
    expect(() => OrganizationGraphEdgeKindSchema.parse('writes')).not.toThrow()
    expect(() => OrganizationGraphEdgeKindSchema.parse('uses_catalog')).not.toThrow()
    expect(() => OrganizationGraphEdgeKindSchema.parse('exposes')).toThrow()
    expect(() => OrganizationGraphEdgeKindSchema.parse(['operates', 'on'].join('-'))).toThrow()
  })

  it('projects compiled ontology records as ontology-native nodes and edges', () => {
    const model = resolveOrganizationModel({
      systems: {
        'test.ontology': {
          id: 'test.ontology',
          order: 10,
          label: 'Ontology System',
          enabled: true,
          ontology: {
            objectTypes: {
              'test.ontology:object/deal': {
                id: 'test.ontology:object/deal',
                label: 'Deal'
              },
              'test.ontology:object/contact': {
                id: 'test.ontology:object/contact',
                label: 'Contact'
              }
            },
            linkTypes: {
              'test.ontology:link/deal-contact': {
                id: 'test.ontology:link/deal-contact',
                label: 'Deal Contact',
                from: 'test.ontology:object/deal',
                to: 'test.ontology:object/contact'
              }
            },
            actionTypes: {
              'test.ontology:action/update-deal': {
                id: 'test.ontology:action/update-deal',
                label: 'Update Deal',
                actsOn: ['test.ontology:object/deal']
              }
            },
            catalogTypes: {
              'test.ontology:catalog/pipeline': {
                id: 'test.ontology:catalog/pipeline',
                label: 'Pipeline',
                appliesTo: 'test.ontology:object/deal'
              }
            }
          }
        }
      }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(graph.nodes.find((node) => node.id === 'ontology:test.ontology:object/deal')).toMatchObject({
      kind: 'ontology',
      sourceId: 'test.ontology:object/deal',
      label: 'Deal',
      ontologyKind: 'object'
    })
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'contains' &&
          edge.sourceId === 'system:test.ontology' &&
          edge.targetId === 'ontology:test.ontology:object/deal'
      )
    ).toBeDefined()
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'links' &&
          edge.sourceId === 'ontology:test.ontology:object/deal' &&
          edge.targetId === 'ontology:test.ontology:object/contact'
      )
    ).toMatchObject({ label: 'Deal Contact' })
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'affects' &&
          edge.sourceId === 'ontology:test.ontology:action/update-deal' &&
          edge.targetId === 'ontology:test.ontology:object/deal'
      )
    ).toBeDefined()
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'applies_to' &&
          edge.sourceId === 'ontology:test.ontology:catalog/pipeline' &&
          edge.targetId === 'ontology:test.ontology:object/deal'
      )
    ).toBeDefined()
  })

  it('projects ontology-native records from recursively authored systems without content-node bridge input', () => {
    const model = resolveOrganizationModel({
      systems: {
        operations: {
          id: 'operations',
          order: 10,
          label: 'Operations',
          enabled: true,
          systems: {
            delivery: {
              id: 'operations.delivery',
              order: 20,
              label: 'Delivery',
              enabled: true,
              ontology: {
                objectTypes: {
                  'operations.delivery:object/project': {
                    id: 'operations.delivery:object/project',
                    label: 'Project'
                  }
                },
                catalogTypes: {
                  'operations.delivery:catalog/project-status': {
                    id: 'operations.delivery:catalog/project-status',
                    label: 'Project Status',
                    kind: 'status-flow',
                    appliesTo: 'operations.delivery:object/project'
                  }
                }
              }
            }
          }
        }
      }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(graph.nodes.find((node) => node.id === 'system:operations.delivery')).toMatchObject({
      kind: 'system',
      sourceId: 'operations.delivery',
      label: 'Delivery'
    })
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'contains' &&
          edge.sourceId === 'system:operations' &&
          edge.targetId === 'system:operations.delivery'
      )
    ).toBeDefined()
    expect(graph.nodes.find((node) => node.id === 'ontology:operations.delivery:catalog/project-status')).toMatchObject(
      {
        kind: 'ontology',
        sourceId: 'operations.delivery:catalog/project-status',
        ontologyKind: 'catalog'
      }
    )
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'applies_to' &&
          edge.sourceId === 'ontology:operations.delivery:catalog/project-status' &&
          edge.targetId === 'ontology:operations.delivery:object/project'
      )
    ).toBeDefined()
    expect(graph.nodes.some((node) => node.id.startsWith('content-node:operations.delivery:'))).toBe(false)
  })

  it('keeps subsystems as a compatibility alias for recursive graph traversal', () => {
    const model = resolveOrganizationModel({
      systems: {
        legacy: {
          id: 'legacy',
          order: 10,
          label: 'Legacy',
          enabled: true,
          subsystems: {
            child: {
              id: 'legacy.child',
              order: 20,
              label: 'Legacy Child',
              enabled: true
            }
          }
        }
      }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(graph.nodes.find((node) => node.id === 'system:legacy.child')).toMatchObject({
      kind: 'system',
      sourceId: 'legacy.child',
      label: 'Legacy Child'
    })
  })

  it('resolves first-class system config without requiring config:kv content', () => {
    const model = resolveOrganizationModel({
      systems: {
        configurable: {
          id: 'configurable',
          order: 10,
          label: 'Configurable',
          enabled: true,
          config: {
            retries: 3,
            mode: 'direct',
            nested: {
              source: 'system.config',
              enabled: true
            }
          }
        }
      }
    })

    expect(resolveSystemConfig(model, 'configurable')).toEqual({
      retries: 3,
      mode: 'direct',
      nested: {
        source: 'system.config',
        enabled: true
      }
    })
    expect(model.systems.configurable?.content).toBeUndefined()
  })


  it('projects resource ontology bindings as resource-to-ontology edges', () => {
    const model = resolveOrganizationModel({
      systems: {
        'test.bindings': {
          id: 'test.bindings',
          order: 10,
          label: 'Binding System',
          enabled: true,
          ontology: {
            objectTypes: {
              'test.bindings:object/deal': {
                id: 'test.bindings:object/deal',
                label: 'Deal'
              }
            },
            actionTypes: {
              'test.bindings:action/update-deal': {
                id: 'test.bindings:action/update-deal',
                label: 'Update Deal'
              }
            },
            catalogTypes: {
              'test.bindings:catalog/pipeline': {
                id: 'test.bindings:catalog/pipeline',
                label: 'Pipeline'
              }
            },
            eventTypes: {
              'test.bindings:event/deal-updated': {
                id: 'test.bindings:event/deal-updated',
                label: 'Deal Updated'
              }
            }
          }
        }
      },
      resources: {
        'deal-workflow': {
          id: 'deal-workflow',
          order: 10,
          kind: 'workflow',
          systemPath: 'test.bindings',
          status: 'active',
          ontology: {
            actions: ['test.bindings:action/update-deal'],
            primaryAction: 'test.bindings:action/update-deal',
            reads: ['test.bindings:object/deal'],
            writes: ['test.bindings:object/deal'],
            usesCatalogs: ['test.bindings:catalog/pipeline'],
            emits: ['test.bindings:event/deal-updated']
          }
        }
      }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'actions' &&
          edge.sourceId === 'resource:deal-workflow' &&
          edge.targetId === 'ontology:test.bindings:action/update-deal'
      )
    ).toBeDefined()
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'reads' &&
          edge.sourceId === 'resource:deal-workflow' &&
          edge.targetId === 'ontology:test.bindings:object/deal'
      )
    ).toBeDefined()
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'writes' &&
          edge.sourceId === 'resource:deal-workflow' &&
          edge.targetId === 'ontology:test.bindings:object/deal'
      )
    ).toBeDefined()
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'uses_catalog' &&
          edge.sourceId === 'resource:deal-workflow' &&
          edge.targetId === 'ontology:test.bindings:catalog/pipeline'
      )
    ).toBeDefined()
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'emits' &&
          edge.sourceId === 'resource:deal-workflow' &&
          edge.targetId === 'ontology:test.bindings:event/deal-updated'
      )
    ).toBeDefined()
  })

  it('projects topology relationships without collapsing ontology binding edges', () => {
    const model = resolveOrganizationModel({
      systems: {
        'test.topology': {
          id: 'test.topology',
          order: 10,
          label: 'Topology System',
          enabled: true,
          ontology: {
            actionTypes: {
              'test.topology:action/discover-email': {
                id: 'test.topology:action/discover-email',
                label: 'Discover Email'
              }
            }
          }
        }
      },
      resources: {
        'email-discovery': {
          id: 'email-discovery',
          order: 10,
          kind: 'workflow',
          systemPath: 'test.topology',
          status: 'active',
          ontology: {
            actions: ['test.topology:action/discover-email'],
            primaryAction: 'test.topology:action/discover-email'
          }
        },
        'email-verification': {
          id: 'email-verification',
          order: 20,
          kind: 'workflow',
          systemPath: 'test.topology',
          status: 'active'
        }
      },
      topology: {
        version: 1,
        relationships: {
          'email-discovery-triggers-email-verification': {
            from: { kind: 'resource', id: 'email-discovery' },
            kind: 'triggers',
            to: { kind: 'resource', id: 'email-verification' },
            required: true
          },
          'email-verification-approval-human-review': {
            from: { kind: 'resource', id: 'email-verification' },
            kind: 'approval',
            to: { kind: 'humanCheckpoint', id: 'email-review' }
          }
        }
      }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'actions' &&
          edge.sourceId === 'resource:email-discovery' &&
          edge.targetId === 'ontology:test.topology:action/discover-email'
      )
    ).toBeDefined()
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'triggers' &&
          edge.relationshipType === 'triggers' &&
          edge.sourceId === 'resource:email-discovery' &&
          edge.targetId === 'resource:email-verification'
      )
    ).toBeDefined()
    expect(graph.nodes.find((node) => node.id === 'resource:email-review')).toMatchObject({
      kind: 'resource',
      resourceType: 'human_checkpoint'
    })
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'approval' &&
          edge.relationshipType === 'approval' &&
          edge.sourceId === 'resource:email-verification' &&
          edge.targetId === 'resource:email-review'
      )
    ).toBeDefined()
  })

  it('projects surface and navigation-group nodes from recursive sidebar leaves', () => {
    const model = resolveOrganizationModel({
      systems: {
        sales: TEST_SYSTEMS.sales
      },
      actions: { [TEST_ACTION.id]: TEST_ACTION },
      navigation: {
        sidebar: {
          primary: {
            workspace: {
              type: 'group',
              label: 'Workspace',
              order: 900,
              children: {
                nested: {
                  type: 'group',
                  label: 'Nested',
                  order: 10,
                  children: {
                    'custom.surface': {
                      type: 'surface',
                      label: 'Custom Surface',
                      path: '/custom-surface',
                      surfaceType: 'page',
                      order: 10,
                      targets: { systems: ['sales'] }
                    }
                  }
                }
              }
            }
          },
          bottom: {}
        }
      }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(graph.nodes.find((node) => node.id === 'navigation-group:workspace')).toMatchObject({
      kind: 'navigation-group',
      label: 'Workspace'
    })
    expect(graph.nodes.find((node) => node.id === 'navigation-group:nested')).toMatchObject({
      kind: 'navigation-group',
      label: 'Nested'
    })
    expect(graph.nodes.find((node) => node.id === 'surface:custom.surface')).toMatchObject({
      kind: 'surface',
      label: 'Custom Surface',
      sourceId: 'custom.surface'
    })
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'contains' &&
          edge.sourceId === 'navigation-group:workspace' &&
          edge.targetId === 'navigation-group:nested'
      )
    ).toBeDefined()
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'contains' &&
          edge.sourceId === 'navigation-group:nested' &&
          edge.targetId === 'surface:custom.surface'
      )
    ).toBeDefined()
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'applies_to' && edge.sourceId === 'surface:custom.surface' && edge.targetId === 'system:sales'
      )
    ).toBeDefined()
  })

  it('bridges command view resources and relationships', () => {
    const model = resolveOrganizationModel()
    const graph = buildOrganizationGraph({
      organizationModel: model,
      commandViewData: {
        generatedAt: '2026-04-24T00:00:00.000Z',
        workflows: [
          {
            resourceId: 'workflow-order',
            name: 'Order Workflow',
            description: 'Order workflow',
            type: 'workflow',
            links: [],
            status: 'active'
          }
        ],
        agents: [],
        triggers: [],
        integrations: [],
        externalResources: [],
        humanCheckpoints: [],
        edges: [
          {
            id: 'edge-1',
            source: 'workflow-order',
            target: 'missing-resource',
            relationship: 'uses'
          }
        ],
        stats: {
          totalResources: 1,
          activeResources: 1,
          inactiveResources: 0,
          resourcesByType: {}
        }
      }
    })

    expect(graph.nodes.find((node) => node.id === 'resource:workflow-order')).toMatchObject({
      kind: 'resource',
      sourceId: 'workflow-order',
      label: 'Order Workflow'
    })
    expect(graph.nodes.find((node) => node.id === 'resource:missing-resource')).toMatchObject({
      kind: 'resource',
      label: 'missing-resource'
    })
    expect(graph.edges.find((edge) => edge.kind === 'references' && edge.relationshipType === 'uses')).toMatchObject({
      kind: 'references',
      relationshipType: 'uses'
    })
  })


  it('projects action nodes from the actions domain with maps_to edges to resources', () => {
    const action = TEST_ACTION
    const graph = buildOrganizationGraph({
      organizationModel: resolveOrganizationModel({
        systems: TEST_SYSTEMS,
        actions: { [action.id]: action },
        resources: {
          [action.resourceId]: {
            id: action.resourceId,
            order: 10,
            kind: 'workflow',
            systemPath: 'sales.lead-gen',
            status: 'active'
          }
        }
      })
    })

    const actionNodes = graph.nodes.filter((node) => node.kind === 'action')
    expect(actionNodes).toHaveLength(1)

    const node = graph.nodes.find((n) => n.id === `action:${action.id}`)
    expect(node).toMatchObject({
      kind: 'action',
      sourceId: action.id,
      label: action.label,
      description: action.description
    })

    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'maps_to' &&
          edge.sourceId === `action:${action.id}` &&
          edge.targetId === `resource:${action.resourceId}`
      )
    ).toBeDefined()

    expect(graph.nodes.find((n) => n.id === `resource:${action.resourceId}`)).toBeDefined()
  })
  it('projects ontology catalog nodes without content-node bridge output', () => {
    const model = resolveOrganizationModel({
      systems: {
        'test.pipeline-sys': {
          id: 'test.pipeline-sys',
          order: 10,
          label: 'Pipeline System',
          enabled: true,
          ontology: {
            objectTypes: {
              'test.pipeline-sys:object/deal': {
                id: 'test.pipeline-sys:object/deal',
                label: 'Deal',
                ownerSystemId: 'test.pipeline-sys'
              }
            },
            catalogTypes: {
              'test.pipeline-sys:catalog/deal-pipeline': {
                id: 'test.pipeline-sys:catalog/deal-pipeline',
                label: 'Deal Pipeline',
                kind: 'pipeline',
                appliesTo: 'test.pipeline-sys:object/deal',
                entries: {
                  'closed-won': { label: 'Closed Won', order: 10, semanticClass: 'closed_won' }
                }
              }
            }
          }
        }
      }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(graph.nodes.find((n) => n.id === 'ontology:test.pipeline-sys:catalog/deal-pipeline')).toMatchObject({
      kind: 'ontology',
      sourceId: 'test.pipeline-sys:catalog/deal-pipeline',
      label: 'Deal Pipeline',
      ontologyKind: 'catalog'
    })
    expect(graph.nodes.some((n) => n.id.startsWith('content-node:'))).toBe(false)
    expect(
      graph.edges.find(
        (e) =>
          e.kind === 'applies_to' &&
          e.sourceId === 'ontology:test.pipeline-sys:catalog/deal-pipeline' &&
          e.targetId === 'ontology:test.pipeline-sys:object/deal'
      )
    ).toBeDefined()
  })

  it('projects status-flow catalog entries through ontology rather than content parent chains', () => {
    const model = resolveOrganizationModel({
      systems: {
        'test.status-sys': {
          id: 'test.status-sys',
          order: 10,
          label: 'Status System',
          enabled: true,
          ontology: {
            objectTypes: {
              'test.status-sys:object/task': {
                id: 'test.status-sys:object/task',
                label: 'Task',
                ownerSystemId: 'test.status-sys'
              }
            },
            catalogTypes: {
              'test.status-sys:catalog/task-status-flow': {
                id: 'test.status-sys:catalog/task-status-flow',
                label: 'Task Status Flow',
                kind: 'status-flow',
                appliesTo: 'test.status-sys:object/task',
                entries: {
                  approved: { label: 'Approved', order: 10 },
                  rejected: { label: 'Rejected', order: 20 }
                }
              }
            }
          }
        }
      }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(graph.nodes.find((n) => n.id === 'ontology:test.status-sys:catalog/task-status-flow')).toMatchObject({
      kind: 'ontology',
      label: 'Task Status Flow'
    })
    expect(graph.nodes.some((n) => n.id.startsWith('content-node:test.status-sys:'))).toBe(false)
  })

  it('projects nested system ontology catalogs with scoped ontology node ids', () => {
    const model = resolveOrganizationModel({
      systems: {
        ops: {
          id: 'ops',
          order: 10,
          label: 'Ops',
          enabled: true,
          systems: {
            delivery: {
              id: 'ops.delivery',
              order: 20,
              label: 'Delivery',
              enabled: true,
              ontology: {
                objectTypes: {
                  'ops.delivery:object/list': {
                    id: 'ops.delivery:object/list',
                    label: 'List',
                    ownerSystemId: 'ops.delivery'
                  }
                },
                catalogTypes: {
                  'ops.delivery:catalog/onboarding-template': {
                    id: 'ops.delivery:catalog/onboarding-template',
                    label: 'Onboarding Template',
                    kind: 'template',
                    appliesTo: 'ops.delivery:object/list',
                    entries: {
                      default: { label: 'Default' }
                    }
                  }
                }
              }
            }
          }
        }
      }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(graph.nodes.find((n) => n.id === 'ontology:ops.delivery:catalog/onboarding-template')).toMatchObject({
      kind: 'ontology',
      sourceId: 'ops.delivery:catalog/onboarding-template',
      label: 'Onboarding Template'
    })
    expect(graph.nodes.some((n) => n.id.startsWith('content-node:ops.delivery:'))).toBe(false)
  })

  it('projects entity nodes with system contains edges and entity links', () => {
    const graph = buildOrganizationGraph({
      organizationModel: resolveOrganizationModel({
        systems: TEST_SYSTEMS,
        actions: { [TEST_ACTION.id]: TEST_ACTION },
        entities: TEST_ENTITIES
      })
    })

    const entityNodes = graph.nodes.filter((node) => node.kind === 'entity')
    expect(entityNodes).toHaveLength(Object.keys(TEST_ENTITIES).length)
    expect(graph.nodes.find((node) => node.id === 'entity:crm.deal')).toMatchObject({
      kind: 'entity',
      sourceId: 'crm.deal',
      label: 'Deal'
    })
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'contains' && edge.sourceId === 'system:sales.crm' && edge.targetId === 'entity:crm.deal'
      )
    ).toBeDefined()
    expect(
      graph.edges.find(
        (edge) => edge.kind === 'links' && edge.sourceId === 'entity:crm.deal' && edge.targetId === 'entity:crm.contact'
      )
    ).toMatchObject({ label: 'contacts' })
  })

  it('projects affects edges from actions to entities', () => {
    const model = resolveOrganizationModel({
      systems: TEST_SYSTEMS,
      entities: TEST_ENTITIES,
      actions: {
        [TEST_ACTION.id]: TEST_ACTION,
        'crm.deal.update': {
          id: 'crm.deal.update',
          order: 10,
          label: 'Update deal',
          affects: ['crm.deal']
        }
      }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'affects' && edge.sourceId === 'action:crm.deal.update' && edge.targetId === 'entity:crm.deal'
      )
    ).toBeDefined()
  })

  it('projects event nodes from workflow and agent resource emission traits', () => {
    const model = resolveOrganizationModel({
      systems: TEST_SYSTEMS,
      actions: { [TEST_ACTION.id]: TEST_ACTION },
      resources: {
        'leadgen-reply-workflow': {
          id: 'leadgen-reply-workflow',
          order: 10,
          kind: 'workflow',
          systemPath: 'sales.lead-gen',
          status: 'active',
          emits: [{ eventKey: 'processed', label: 'Reply Processed', payloadSchema: 'leadgen.reply' }]
        },
        'proposal-agent': {
          id: 'proposal-agent',
          order: 20,
          kind: 'agent',
          systemPath: 'sales.crm',
          status: 'active',
          agentKind: 'specialist',
          sessionCapable: true,
          emits: [{ eventKey: 'approved', label: 'Proposal Approved' }]
        },
        'crm-integration': {
          id: 'crm-integration',
          order: 30,
          kind: 'integration',
          systemPath: 'sales.crm',
          status: 'active',
          provider: 'attio'
        }
      }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(graph.nodes.find((node) => node.id === 'event:leadgen-reply-workflow:processed')).toMatchObject({
      kind: 'event',
      sourceId: 'leadgen-reply-workflow:processed',
      label: 'Reply Processed'
    })
    expect(graph.nodes.find((node) => node.id === 'event:proposal-agent:approved')).toMatchObject({
      kind: 'event',
      sourceId: 'proposal-agent:approved',
      label: 'Proposal Approved'
    })
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'emits' &&
          edge.sourceId === 'resource:leadgen-reply-workflow' &&
          edge.targetId === 'event:leadgen-reply-workflow:processed'
      )
    ).toBeDefined()
    expect(graph.nodes.some((node) => node.id.startsWith('event:crm-integration:'))).toBe(false)
  })


  it('links event-triggered policies to projected event nodes', () => {
    const action = TEST_ACTION
    const model = resolveOrganizationModel({
      ...BASE_GRAPH_MODEL,
      resources: {
        'leadgen-reply-workflow': {
          id: 'leadgen-reply-workflow',
          order: 10,
          kind: 'workflow',
          systemPath: 'sales.lead-gen',
          status: 'active',
          emits: [{ eventKey: 'processed', label: 'Reply Processed' }]
        }
      },
      policies: {
        'leadgen.reply.review': {
          id: 'leadgen.reply.review',
          order: 10,
          label: 'Review processed replies',
          trigger: { kind: 'event', eventId: 'leadgen-reply-workflow:processed' },
          actions: [{ kind: 'invoke-action', actionId: action.id }],
          appliesTo: {
            systemIds: ['sales.lead-gen'],
            actionIds: [],
            resourceIds: ['leadgen-reply-workflow'],
            roleIds: []
          }
        }
      }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(graph.nodes.find((node) => node.id === 'policy:leadgen.reply.review')).toMatchObject({
      kind: 'policy',
      sourceId: 'leadgen.reply.review'
    })
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'triggers' &&
          edge.sourceId === 'event:leadgen-reply-workflow:processed' &&
          edge.targetId === 'policy:leadgen.reply.review'
      )
    ).toBeDefined()
  })

  it('emits uses edges from systems to attached actions', () => {
    const graph = buildOrganizationGraph({ organizationModel: resolveOrganizationModel(BASE_GRAPH_MODEL) })

    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'uses' &&
          edge.sourceId === 'system:sales.lead-gen' &&
          edge.targetId === 'action:lead-gen.company.source'
      )
    ).toMatchObject({ label: 'exposes' })
  })

  it('derives resource membership and agent invocation edges from canonical OM fields', () => {
    const action = TEST_ACTION
    const model = resolveOrganizationModel({
      ...BASE_GRAPH_MODEL,
      resources: {
        'ops-agent': {
          id: 'ops-agent',
          order: 10,
          kind: 'agent',
          systemPath: 'operations',
          status: 'active',
          agentKind: 'orchestrator',
          sessionCapable: true,
          invocations: [action.invocations[0]!, { kind: 'script-execution', resourceId: 'ops-script' }]
        },
        'ops-script': {
          id: 'ops-script',
          order: 20,
          kind: 'script',
          systemPath: 'operations',
          status: 'active',
          language: 'typescript',
          source: { file: 'scripts/ops.ts' }
        }
      }
    })
    const graph = buildOrganizationGraph({ organizationModel: model })

    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'contains' && edge.sourceId === 'system:operations' && edge.targetId === 'resource:ops-agent'
      )
    ).toBeDefined()
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'uses' && edge.sourceId === 'system:sales.lead-gen' && edge.targetId === `action:${action.id}`
      )
    ).toMatchObject({ label: 'exposes' })
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'references' &&
          edge.sourceId === 'resource:ops-agent' &&
          edge.targetId === `action:${action.id}`
      )
    ).toMatchObject({ label: 'POST /api/prospecting/companies/source' })
    expect(
      graph.edges.find(
        (edge) =>
          edge.kind === 'uses' && edge.sourceId === 'resource:ops-agent' && edge.targetId === 'resource:ops-script'
      )
    ).toMatchObject({ label: 'script ops-script' })
  })


  describe('policy edges in graph projection', () => {
    const action = TEST_ACTION

    it('event-triggered policy emits triggers edge from event node to policy node', () => {
      const model = resolveOrganizationModel({
        ...BASE_GRAPH_MODEL,
        resources: {
          'reply-workflow': {
            id: 'reply-workflow',
            order: 10,
            kind: 'workflow',
            systemPath: 'sales.lead-gen',
            status: 'active',
            emits: [{ eventKey: 'processed', label: 'Reply Processed' }]
          }
        },
        policies: {
          'policy.reply.review': {
            id: 'policy.reply.review',
            order: 10,
            label: 'Review Reply',
            trigger: { kind: 'event', eventId: 'reply-workflow:processed' },
            actions: [{ kind: 'block' }]
          }
        }
      })
      const graph = buildOrganizationGraph({ organizationModel: model })

      expect(
        graph.edges.find(
          (edge) =>
            edge.kind === 'triggers' &&
            edge.sourceId === 'event:reply-workflow:processed' &&
            edge.targetId === 'policy:policy.reply.review'
        )
      ).toBeDefined()
    })

    it('action-invocation-triggered policy emits triggers edge from action node to policy node', () => {
      const model = resolveOrganizationModel({
        ...BASE_GRAPH_MODEL,
        policies: {
          'policy.action.gate': {
            id: 'policy.action.gate',
            order: 10,
            label: 'Action Gate',
            trigger: { kind: 'action-invocation', actionId: action.id },
            actions: [{ kind: 'require-approval' }]
          }
        }
      })
      const graph = buildOrganizationGraph({ organizationModel: model })

      expect(
        graph.edges.find(
          (edge) =>
            edge.kind === 'triggers' &&
            edge.sourceId === `action:${action.id}` &&
            edge.targetId === 'policy:policy.action.gate'
        )
      ).toBeDefined()
    })

    it('invoke-action effect emits effects edge from policy to target action', () => {
      const model = resolveOrganizationModel({
        ...BASE_GRAPH_MODEL,
        policies: {
          'policy.invoke': {
            id: 'policy.invoke',
            order: 10,
            label: 'Invoke Action Policy',
            trigger: { kind: 'manual' },
            actions: [{ kind: 'invoke-action', actionId: action.id }]
          }
        }
      })
      const graph = buildOrganizationGraph({ organizationModel: model })

      expect(
        graph.edges.find(
          (edge) =>
            edge.kind === 'effects' &&
            edge.sourceId === 'policy:policy.invoke' &&
            edge.targetId === `action:${action.id}`
        )
      ).toMatchObject({ label: 'invoke action' })
    })

    it('notify-role effect emits effects edge from policy to target role', () => {
      const model = resolveOrganizationModel({
        roles: {
          'role.notify-target': {
            id: 'role.notify-target',
            order: 10,
            title: 'Notify Target',
            responsibleFor: []
          }
        },
        policies: {
          'policy.notify': {
            id: 'policy.notify',
            order: 10,
            label: 'Notify Policy',
            trigger: { kind: 'manual' },
            actions: [{ kind: 'notify-role', roleId: 'role.notify-target' }]
          }
        }
      })
      const graph = buildOrganizationGraph({ organizationModel: model })

      expect(
        graph.edges.find(
          (edge) =>
            edge.kind === 'effects' &&
            edge.sourceId === 'policy:policy.notify' &&
            edge.targetId === 'role:role.notify-target'
        )
      ).toMatchObject({ label: 'notify-role' })
    })

    it('require-approval effect with roleId emits effects edge to role', () => {
      const model = resolveOrganizationModel({
        roles: {
          'role.approver': {
            id: 'role.approver',
            order: 10,
            title: 'Approver',
            responsibleFor: []
          }
        },
        policies: {
          'policy.approval': {
            id: 'policy.approval',
            order: 10,
            label: 'Approval Policy',
            trigger: { kind: 'manual' },
            actions: [{ kind: 'require-approval', roleId: 'role.approver' }]
          }
        }
      })
      const graph = buildOrganizationGraph({ organizationModel: model })

      expect(
        graph.edges.find(
          (edge) =>
            edge.kind === 'effects' &&
            edge.sourceId === 'policy:policy.approval' &&
            edge.targetId === 'role:role.approver'
        )
      ).toMatchObject({ label: 'require-approval' })
    })

    it('appliesTo.systemIds emits applies_to edges to each system', () => {
      const model = resolveOrganizationModel({
        systems: TEST_SYSTEMS,
        actions: { [TEST_ACTION.id]: TEST_ACTION },
        policies: {
          'policy.sys-scope': {
            id: 'policy.sys-scope',
            order: 10,
            label: 'System Scope Policy',
            trigger: { kind: 'manual' },
            actions: [{ kind: 'block' }],
            appliesTo: { systemIds: ['sales.lead-gen', 'sales.crm'] }
          }
        }
      })
      const graph = buildOrganizationGraph({ organizationModel: model })

      expect(
        graph.edges.find(
          (edge) =>
            edge.kind === 'applies_to' &&
            edge.sourceId === 'policy:policy.sys-scope' &&
            edge.targetId === 'system:sales.lead-gen'
        )
      ).toBeDefined()
      expect(
        graph.edges.find(
          (edge) =>
            edge.kind === 'applies_to' &&
            edge.sourceId === 'policy:policy.sys-scope' &&
            edge.targetId === 'system:sales.crm'
        )
      ).toBeDefined()
    })

    it('appliesTo.actionIds emits applies_to edges to each action', () => {
      const model = resolveOrganizationModel({
        ...BASE_GRAPH_MODEL,
        policies: {
          'policy.action-scope': {
            id: 'policy.action-scope',
            order: 10,
            label: 'Action Scope Policy',
            trigger: { kind: 'manual' },
            actions: [{ kind: 'block' }],
            appliesTo: { actionIds: [action.id] }
          }
        }
      })
      const graph = buildOrganizationGraph({ organizationModel: model })

      expect(
        graph.edges.find(
          (edge) =>
            edge.kind === 'applies_to' &&
            edge.sourceId === 'policy:policy.action-scope' &&
            edge.targetId === `action:${action.id}`
        )
      ).toBeDefined()
    })

    it('appliesTo.resourceIds emits applies_to edges to each resource', () => {
      const model = resolveOrganizationModel({
        systems: TEST_SYSTEMS,
        actions: { [TEST_ACTION.id]: TEST_ACTION },
        resources: {
          'scoped-workflow': {
            id: 'scoped-workflow',
            order: 10,
            kind: 'workflow',
            systemPath: 'sales.lead-gen',
            status: 'active'
          }
        },
        policies: {
          'policy.res-scope': {
            id: 'policy.res-scope',
            order: 10,
            label: 'Resource Scope Policy',
            trigger: { kind: 'manual' },
            actions: [{ kind: 'block' }],
            appliesTo: { resourceIds: ['scoped-workflow'] }
          }
        }
      })
      const graph = buildOrganizationGraph({ organizationModel: model })

      expect(
        graph.edges.find(
          (edge) =>
            edge.kind === 'applies_to' &&
            edge.sourceId === 'policy:policy.res-scope' &&
            edge.targetId === 'resource:scoped-workflow'
        )
      ).toBeDefined()
    })

    it('appliesTo.roleIds emits applies_to edges to each role', () => {
      const model = resolveOrganizationModel({
        roles: {
          'role.scoped': {
            id: 'role.scoped',
            order: 10,
            title: 'Scoped Role',
            responsibleFor: []
          }
        },
        policies: {
          'policy.role-scope': {
            id: 'policy.role-scope',
            order: 10,
            label: 'Role Scope Policy',
            trigger: { kind: 'manual' },
            actions: [{ kind: 'block' }],
            appliesTo: { roleIds: ['role.scoped'] }
          }
        }
      })
      const graph = buildOrganizationGraph({ organizationModel: model })

      expect(
        graph.edges.find(
          (edge) =>
            edge.kind === 'applies_to' &&
            edge.sourceId === 'policy:policy.role-scope' &&
            edge.targetId === 'role:role.scoped'
        )
      ).toBeDefined()
    })

    it('triggers edge is silently skipped when eventId is not projected (no error thrown)', () => {
      const model = resolveOrganizationModel({
        policies: {
          'policy.dangling-event': {
            id: 'policy.dangling-event',
            order: 10,
            label: 'Dangling Event Policy',
            trigger: { kind: 'event', eventId: 'nonexistent-workflow:missing-event' },
            actions: [{ kind: 'block' }]
          }
        }
      })

      expect(() => buildOrganizationGraph({ organizationModel: model })).not.toThrow()

      const graph = buildOrganizationGraph({ organizationModel: model })
      expect(
        graph.edges.find((edge) => edge.kind === 'triggers' && edge.targetId === 'policy:policy.dangling-event')
      ).toBeUndefined()
    })
  })
})
