/**
 * @module tests/unit/types/reserved-metadata-keys.test-d
 * @description Compile-time tests for the reserved-field contract (layer 1 of
 * three — see src/types/reservedFields.ts): a literal reserved key inside any
 * `metadata` param is a TypeScript error, while the generic `T` ergonomics
 * stay intact (typed bags, untyped brains, index-signature shapes, and the
 * documented exemption for consumers who explicitly declare a reserved key in
 * their own metadata type).
 *
 * Runs under vitest typecheck mode (`test.typecheck` in
 * tests/configs/vitest.unit.config.ts) — these assertions are validated by
 * `tsc`, never executed. The runtime half of the contract (the write-path
 * remap for untyped callers) is pinned by
 * tests/unit/brainy/update-reserved-metadata-remap.test.ts.
 */

import { describe, it, assertType } from 'vitest'
import type {
  AddParams,
  UpdateParams,
  RelateParams,
  UpdateRelationParams,
  TxOperation
} from '../../../src/index.js'
import { NounType, VerbType } from '../../../src/types/graphTypes.js'

describe('reserved entity keys in metadata are compile errors', () => {
  it('AddParams (untyped brain) rejects every reserved key but stays open for custom fields', () => {
    // Custom fields of any shape remain legal — exactly the pre-8.0 latitude.
    assertType<AddParams>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      metadata: { dept: 'eng', level: 3, tags: ['a', 'b'], nested: { ok: true } }
    })

    assertType<AddParams>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      // @ts-expect-error — 'noun' is reserved (the entity type travels via the top-level 'type' param)
      metadata: { noun: 'organization' }
    })
    assertType<AddParams>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      // @ts-expect-error — 'subtype' is reserved (use the top-level 'subtype' param)
      metadata: { subtype: 'contractor' }
    })
    assertType<AddParams>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      // @ts-expect-error — 'createdAt' is reserved (system-managed)
      metadata: { createdAt: Date.now() }
    })
    assertType<AddParams>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      // @ts-expect-error — 'updatedAt' is reserved (system-managed)
      metadata: { updatedAt: Date.now() }
    })
    assertType<AddParams>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      // @ts-expect-error — 'confidence' is reserved (use the top-level 'confidence' param)
      metadata: { confidence: 0.8 }
    })
    assertType<AddParams>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      // @ts-expect-error — 'weight' is reserved (use the top-level 'weight' param)
      metadata: { weight: 0.5 }
    })
    assertType<AddParams>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      // @ts-expect-error — 'service' is reserved (use the top-level 'service' param)
      metadata: { service: 'orders' }
    })
    assertType<AddParams>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      // @ts-expect-error — 'data' is reserved (use the top-level 'data' param)
      metadata: { data: 'content' }
    })
    assertType<AddParams>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      // @ts-expect-error — 'createdBy' is reserved (use the top-level 'createdBy' param)
      metadata: { createdBy: { augmentation: 'importer', version: '1.0' } }
    })
    assertType<AddParams>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      // @ts-expect-error — '_rev' is reserved (system-managed revision counter)
      metadata: { _rev: 7 }
    })
  })

  it('AddParams<T> (typed brain) rejects reserved keys alongside the declared shape', () => {
    interface EmployeeMeta {
      dept: string
      level: number
    }

    assertType<AddParams<EmployeeMeta>>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      metadata: { dept: 'eng', level: 3 }
    })

    assertType<AddParams<EmployeeMeta>>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      // @ts-expect-error — 'confidence' is reserved even when T declares other fields
      metadata: { dept: 'eng', level: 3, confidence: 0.8 }
    })
  })

  it('documented exemptions: T-declared reserved keys and index-signature shapes stay assignable', () => {
    // A consumer who *explicitly* types a reserved key into their metadata
    // shape keeps a working (if unwise) type — the guard exempts keyof T.
    interface LegacyMeta {
      confidence: number
      note: string
    }
    assertType<AddParams<LegacyMeta>>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      metadata: { confidence: 0.8, note: 'declared by the consumer type' }
    })

    // Index-signature metadata types (keyof T = string) remain fully open.
    assertType<AddParams<Record<string, unknown>>>({
      type: NounType.Person,
      subtype: 'employee',
      data: 'x',
      metadata: { anything: 'goes', confidence: 0.8 }
    })
  })

  it('UpdateParams patch rejects reserved keys but accepts partial custom patches', () => {
    interface EmployeeMeta {
      dept: string
      level: number
    }

    // Partial patch of the declared shape is legal.
    assertType<UpdateParams<EmployeeMeta>>({ id: 'e1', metadata: { dept: 'sales' } })
    // Untyped patch with custom fields is legal.
    assertType<UpdateParams>({ id: 'e1', metadata: { status: 'reviewed', rating: 4.5 } })

    // @ts-expect-error — 'confidence' is reserved (use the top-level 'confidence' param)
    assertType<UpdateParams>({ id: 'e1', metadata: { confidence: 0.33 } })
    // @ts-expect-error — 'subtype' is reserved (use the top-level 'subtype' param)
    assertType<UpdateParams>({ id: 'e1', metadata: { subtype: 'specialized' } })
    // @ts-expect-error — '_rev' is reserved (pass 'ifRev' for optimistic concurrency)
    assertType<UpdateParams>({ id: 'e1', metadata: { _rev: 3 } })
    // @ts-expect-error — 'confidence' is reserved even when T declares other fields
    assertType<UpdateParams<EmployeeMeta>>({ id: 'e1', metadata: { confidence: 0.1 } })
  })
})

describe('reserved relationship keys in metadata are compile errors', () => {
  it('RelateParams rejects reserved keys but stays open for custom edge fields', () => {
    assertType<RelateParams>({
      from: 'a',
      to: 'b',
      type: VerbType.ReportsTo,
      subtype: 'direct',
      metadata: { role: 'peer', since: 2024 }
    })

    assertType<RelateParams>({
      from: 'a',
      to: 'b',
      type: VerbType.ReportsTo,
      subtype: 'direct',
      // @ts-expect-error — 'verb' is reserved (the relationship type travels via the top-level 'type' param)
      metadata: { verb: 'relatedTo' }
    })
    assertType<RelateParams>({
      from: 'a',
      to: 'b',
      type: VerbType.ReportsTo,
      subtype: 'direct',
      // @ts-expect-error — 'confidence' is reserved (use the top-level 'confidence' param)
      metadata: { confidence: 0.9 }
    })
    assertType<RelateParams>({
      from: 'a',
      to: 'b',
      type: VerbType.ReportsTo,
      subtype: 'direct',
      // @ts-expect-error — 'weight' is reserved (use the top-level 'weight' param)
      metadata: { weight: 0.4 }
    })
    assertType<RelateParams>({
      from: 'a',
      to: 'b',
      type: VerbType.ReportsTo,
      subtype: 'direct',
      // @ts-expect-error — 'service' is reserved (use the top-level 'service' param)
      metadata: { service: 'orders' }
    })
  })

  it('UpdateRelationParams patch rejects reserved keys', () => {
    assertType<UpdateRelationParams>({ id: 'r1', metadata: { note: 'fine' } })

    // @ts-expect-error — 'confidence' is reserved (use the top-level 'confidence' param)
    assertType<UpdateRelationParams>({ id: 'r1', metadata: { confidence: 0.5 } })
    // @ts-expect-error — 'subtype' is reserved (use the top-level 'subtype' param)
    assertType<UpdateRelationParams>({ id: 'r1', metadata: { subtype: 'dotted-line' } })
    // @ts-expect-error — 'createdAt' is reserved (system-managed)
    assertType<UpdateRelationParams>({ id: 'r1', metadata: { createdAt: 1 } })
  })
})

describe('transact() operations inherit the same guard', () => {
  it('TxOperation add/update/relate metadata rejects reserved keys', () => {
    assertType<TxOperation>({
      op: 'add',
      type: NounType.Concept,
      subtype: 'general',
      data: 'tx',
      metadata: { custom: 'a' }
    })
    assertType<TxOperation>({
      op: 'add',
      type: NounType.Concept,
      subtype: 'general',
      data: 'tx',
      // @ts-expect-error — 'confidence' is reserved on transact add ops too
      metadata: { confidence: 0.7 }
    })
    assertType<TxOperation>({
      op: 'update',
      id: 'e1',
      // @ts-expect-error — 'weight' is reserved on transact update ops too
      metadata: { weight: 0.2 }
    })
    assertType<TxOperation>({
      op: 'relate',
      from: 'a',
      to: 'b',
      type: VerbType.RelatedTo,
      subtype: 'colleague',
      // @ts-expect-error — 'verb' is reserved on transact relate ops too
      metadata: { verb: 'contains' }
    })
  })
})
