/**
 * Package-owned invariant companion for `@deepseek-ai/dsh-client-runtime`.
 * @module @deepseek-ai/dsh-client-runtime/invariant
 */

/* jscpd:ignore-start */
/* eslint-disable @typescript-eslint/no-redundant-type-constituents --
 * `keyof SlotMap & string` is the declare-merge key pattern: SlotMap is empty
 * in this compilation unit (intersection reads `never`) but consumers merge
 * keys in; the rule fires on the empty-map view, not on real redundancy. */
import type { Context } from 'cordis'
import type { SlotMap } from '@deepseek-ai/dsh-client-ui-slots'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'

const PACKAGE_NAME = '@deepseek-ai/dsh-client-runtime'

/** Cordis companion plugin name. */
export const name = 'client-runtime-invariant'
/** Service required before the companion can register. */
export const inject = ['invariants']

/**
 * Owned relation: every 'slots/changed'(key) emission must observe the
 * mutation already applied — SlotCore bumps the key's version synchronously
 * before the service re-emits, so a zero version at dispatch time means the
 * event fired without (or ahead of) its mutation.
 */
const install: InvariantInstaller = (ctx, fail) => {
  ctx.on('internal/dispatch', (_mode, eventName, args) => {
    if (eventName !== 'slots/changed') return
    const key: unknown = args[0]
    if (typeof key !== 'string' || key === '') {
      fail("'slots/changed' dispatched without a slot key argument")
      return
    }
    const slots = ctx.get('slots')
    // Event payloads carry keys as plain strings; getVersion is statically
    // keyed, so restore the SlotMap-key type after the runtime string check.
    if (slots !== undefined && slots.getVersion(key as keyof SlotMap & string) === 0) {
      fail(`'slots/changed' fired for "${key}" before any mutation bumped its version — emission must follow the applied mutation`)
    }
  }, { global: true })
}

/**
 * Register this package's invariant companion.
 * @param ctx - Cordis context carrying the invariant service.
 * @returns the installed registration's disposer after setup succeeds.
 */
export const apply = (ctx: Context): Promise<() => void> =>
  Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
/* jscpd:ignore-end */
