/**
 * Browser-safe shared cross-reference index for the Organization Model.
 *
 * This module is the single source of truth for OM cross-reference resolution.
 * It is consumed by:
 *   - `OrganizationModelSchema.superRefine` (Zod validation, browser-safe)
 *   - `packages/cli/src/knowledge/verify.ts` (CLI gate, Node-only)
 *
 * INVARIANT: No Node.js builtins, no `@repo/elevasis-core` import, no dynamic import().
 */

import { compileOrganizationOntology } from './ontology'
import { listAllSystems } from './helpers'
import type { OntologyCompilation, OntologyKind } from './ontology'
import type { OrganizationModel } from './types'

// ---------------------------------------------------------------------------
// Shared key-to-kind map
// ---------------------------------------------------------------------------

/**
 * Maps ontology reference field keys (as they appear inside ontology records
 * and resource ontology bindings) to their expected OntologyKind.
 *
 * Single source of truth — replaces both the byte-identical
 * `ontologyReferenceKeyKinds` in `schema.ts` and `genericReferenceKeyKinds`
 * in `verify.ts`.
 */
export const ONTOLOGY_REFERENCE_KEY_KINDS: Record<string, OntologyKind> = {
  valueType: 'value-type',
  catalogType: 'catalog',
  objectType: 'object',
  eventType: 'event',
  actionType: 'action',
  linkType: 'link',
  interfaceType: 'interface',
  propertyType: 'property',
  groupType: 'group',
  endpointType: 'endpoint',
  stepCatalog: 'catalog'
} satisfies Record<string, OntologyKind>

// ---------------------------------------------------------------------------
// Cross-reference index
// ---------------------------------------------------------------------------

/**
 * Pre-built look-up sets derived from a resolved OrganizationModel.
 * Built once, then consumed by both validation paths (superRefine + verify.ts).
 */
export interface OmCrossRefIndex {
  /**
   * Systems keyed by BOTH dotted path (e.g. "sales.lead-gen") AND system.id.
   * Value is opaque (`unknown`) — consumers only call `.has()`.
   */
  systemsById: Map<string, unknown>
  resourceIds: Set<string>
  clientIds: Set<string>
  knowledgeIds: Set<string>
  roleIds: Set<string>
  goalIds: Set<string>
  actionIds: Set<string>
  customerSegmentIds: Set<string>
  offeringIds: Set<string>
  ontologyIds: Set<string>
  /** Ontology records indexed by kind (value is a plain id→record map). */
  ontologyIndexByKind: Record<OntologyKind, Record<string, unknown>>
  /** IDs of individual stage entries from all catalogs with `kind === 'stage'`. */
  stageIds: Set<string>
}

export interface OmCompilationContext {
  crossRefIndex: OmCrossRefIndex
  ontologyCompilation: OntologyCompilation
}

const omCompilationContextCache = new WeakMap<OrganizationModel, OmCompilationContext>()

/**
 * Build the OmCrossRefIndex from a resolved OrganizationModel and a compiled
 * ontology result.
 *
 * Call once per validation pass and share the result across all checks.
 */
function buildOmCrossRefIndexFromOntology(
  model: OrganizationModel,
  ontologyCompilation: OntologyCompilation
): OmCrossRefIndex {
  // Systems: keyed by path AND by system.id (path-OR-id resolution)
  const systemsById = new Map<string, unknown>()
  for (const { path, system } of listAllSystems(model)) {
    systemsById.set(path, system)
    systemsById.set(system.id, system)
  }

  const resourceIds = new Set(Object.keys(model.resources ?? {}))
  const clientIds = new Set(Object.keys(model.clients ?? {}))
  const knowledgeIds = new Set(Object.keys(model.knowledge ?? {}))
  const roleIds = new Set(Object.keys(model.roles ?? {}))
  const goalIds = new Set(Object.keys(model.goals ?? {}))
  const actionIds = new Set(Object.keys(model.actions ?? {}))
  const customerSegmentIds = new Set(Object.keys(model.customers ?? {}))
  const offeringIds = new Set(Object.keys(model.offerings ?? {}))

  const ontologyIndexByKind: Record<OntologyKind, Record<string, unknown>> = {
    object: ontologyCompilation.ontology.objectTypes,
    link: ontologyCompilation.ontology.linkTypes,
    action: ontologyCompilation.ontology.actionTypes,
    catalog: ontologyCompilation.ontology.catalogTypes,
    event: ontologyCompilation.ontology.eventTypes,
    interface: ontologyCompilation.ontology.interfaceTypes,
    'value-type': ontologyCompilation.ontology.valueTypes,
    property: ontologyCompilation.ontology.sharedProperties,
    group: ontologyCompilation.ontology.groups,
    endpoint: ontologyCompilation.ontology.endpoints
  }

  const ontologyIds = new Set(Object.values(ontologyIndexByKind).flatMap((index) => Object.keys(index)))

  // Stage IDs: entry keys from all catalogs whose kind === 'stage'
  const stageIds = new Set<string>()
  for (const catalog of Object.values(ontologyCompilation.ontology.catalogTypes)) {
    if ((catalog as { kind?: string }).kind !== 'stage') continue
    const entries = (catalog as { entries?: Record<string, unknown> }).entries
    if (entries !== undefined) {
      for (const stageId of Object.keys(entries)) {
        stageIds.add(stageId)
      }
    }
  }

  return {
    systemsById,
    resourceIds,
    clientIds,
    knowledgeIds,
    roleIds,
    goalIds,
    actionIds,
    customerSegmentIds,
    offeringIds,
    ontologyIds,
    ontologyIndexByKind,
    stageIds
  }
}

/**
 * Build and memoize the shared OM compilation context for a resolved model.
 *
 * This combines ontology compilation and cross-reference indexing so validation
 * gates can reuse one ontology pass while preserving browser-safe imports.
 */
export function buildOmCompilationContext(model: OrganizationModel): OmCompilationContext {
  const cached = omCompilationContextCache.get(model)
  if (cached !== undefined) return cached

  const ontologyCompilation = compileOrganizationOntology(model)
  const crossRefIndex = buildOmCrossRefIndexFromOntology(model, ontologyCompilation)
  const context = { crossRefIndex, ontologyCompilation }
  omCompilationContextCache.set(model, context)
  return context
}

/**
 * Build the OmCrossRefIndex from a resolved OrganizationModel.
 *
 * Back-compat helper retained for existing consumers. New validation paths that
 * also need ontology diagnostics should prefer buildOmCompilationContext().
 */
export function buildOmCrossRefIndex(model: OrganizationModel): OmCrossRefIndex {
  return buildOmCompilationContext(model).crossRefIndex
}

// ---------------------------------------------------------------------------
// Canonical resolution functions
// ---------------------------------------------------------------------------

/**
 * The ONLY definition of which (kind, id) knowledge-link targets resolve.
 *
 * Fixes two confirmed divergences vs. the old inline `exists()` in verify.ts:
 *  1. `kind === 'stage'` — previously missing in verify.ts, causing a false-positive error.
 *  2. `kind === 'system'` — resolves via `systemsById` which is keyed by BOTH path AND
 *     `system.id`, so a system referenced by its id (not its dotted path) is also found.
 */
export function knowledgeTargetExists(index: OmCrossRefIndex, kind: string, id: string): boolean {
  if (kind === 'system') return index.systemsById.has(id)
  if (kind === 'client') return index.clientIds.has(id)
  if (kind === 'resource') return index.resourceIds.has(id)
  if (kind === 'knowledge') return index.knowledgeIds.has(id)
  if (kind === 'stage') return index.stageIds.has(id)
  if (kind === 'action') return index.actionIds.has(id)
  if (kind === 'role') return index.roleIds.has(id)
  if (kind === 'goal') return index.goalIds.has(id)
  if (kind === 'customer-segment') return index.customerSegmentIds.has(id)
  if (kind === 'offering') return index.offeringIds.has(id)
  if (kind === 'ontology') return index.ontologyIds.has(id)
  return false
}

/**
 * Returns `true` when a resource ontology binding references an ontology ID
 * that does not exist in the compiled index for the expected kind.
 *
 * Shared between `schema.ts` resource-ontology binding validation and
 * `verify.ts` resource ontology reference checks.
 */
export function resourceOntologyBindingMissing(
  index: OmCrossRefIndex,
  expectedKind: OntologyKind,
  ontologyId: string
): boolean {
  return index.ontologyIndexByKind[expectedKind][ontologyId] === undefined
}
