import { describe, expect, it, vi } from 'vitest'
import type { DynamicCordisLivePackage } from '@deepseek-ai/dsh-cordis-client-runner/client'
import type {
  CordisDynamicPackageId, CordisDynamicPluginId, CordisDynamicPluginRunId,
  DynamicCordisInventoryRow,
} from '../src/client/events.ts'
import { cordisDefineCard, cordisRunCard } from '../src/client/card-model.ts'
import { CordisRunCardRegistry, cordisToolViewKey } from '../src/client/run-card-index.ts'
import { cordisVisibleStatus } from '../src/client/status.ts'

const PLUGIN = 'clock-1' as CordisDynamicPluginId
const PACKAGE = 'pkg-1' as CordisDynamicPackageId
const RUN = 'run-1' as CordisDynamicPluginRunId

const row = (client: boolean): DynamicCordisInventoryRow => ({
  pluginId: PLUGIN,
  agentId: 'session-1' as DynamicCordisInventoryRow['agentId'],
  packages: [{
    packageId: PACKAGE,
    name: 'Clock',
    purpose: 'show time',
    hasHostHalf: true,
    hasClientHalf: client,
  }],
  currentPackageId: PACKAGE,
  activeRun: { packageId: PACKAGE, pluginRunId: RUN },
})

describe('versioned Cordis card models', () => {
  it('reads symmetric Host and Client source fields from cordis_define', () => {
    const card = cordisDefineCard({
      callId: 'call-1',
      name: 'cordis_define',
      argsRaw: JSON.stringify({
        plugin: { kind: 'new', idPrefix: 'clock' },
        name: 'Clock',
        purpose: 'show time',
        code: { host: 'HOST_CODE', client: 'CLIENT_CODE' },
      }),
      turn: 1,
      step: 1,
      time: 1,
      callView: null,
      subCalls: [],
    })

    expect(card).toMatchObject({
      pluginId: null,
      packageId: null,
      hostCode: 'HOST_CODE',
      clientCode: 'CLIENT_CODE',
      state: 'running',
    })
  })

  it('reads exact activation metadata from a successful cordis_run result', () => {
    const card = cordisRunCard({
      kind: 'tool-result',
      seq: 9,
      time: 2,
      callId: 'call-2',
      call: { name: 'cordis_run', argsRaw: JSON.stringify({ pluginId: PLUGIN, packageId: PACKAGE, mode: 'run' }) },
      callTime: 1,
      content: [{ type: 'text', text: 'running' }],
      isError: false,
      meta: { pluginId: PLUGIN, packageId: PACKAGE, pluginRunId: RUN },
      callView: null,
      resultView: null,
      subCalls: [],
    })

    expect(card).toMatchObject({
      pluginId: PLUGIN,
      packageId: PACKAGE,
      pluginRunId: RUN,
      mode: 'run',
      seq: 9,
      state: 'ok',
    })
  })

  it('keeps the target identities while cordis_run waits for approval', () => {
    const card = cordisRunCard({
      callId: 'call-3',
      name: 'cordis_run',
      argsRaw: JSON.stringify({ pluginId: PLUGIN, packageId: PACKAGE, mode: 'update' }),
      turn: 1,
      step: 1,
      time: 1,
      callView: null,
      subCalls: [],
    })

    expect(card).toMatchObject({
      pluginId: PLUGIN,
      packageId: PACKAGE,
      pluginRunId: null,
      mode: 'update',
      state: 'running',
    })
  })
})

describe('Cordis run-card ownership', () => {
  it('keeps the greatest Session log sequence for one Plugin and Package', () => {
    const store = new CordisRunCardRegistry().forSession('session-1' as DynamicCordisInventoryRow['agentId'])
    const changed = vi.fn()
    store.subscribe(changed)
    const key = cordisToolViewKey(PLUGIN, PACKAGE)

    store.observe({ key, callId: 'new', seq: 20, pluginRunId: RUN })
    store.observe({ key, callId: 'old', seq: 10, pluginRunId: 'run-0' as CordisDynamicPluginRunId })

    expect(store.getSnapshot().get(key)?.callId).toBe('new')
    expect(changed).toHaveBeenCalledTimes(1)
  })
})

describe('Cordis visible status', () => {
  it('distinguishes Host-only running, Client pending, and fully loaded', () => {
    expect(cordisVisibleStatus(row(false), PACKAGE, [])).toBe('running')
    expect(cordisVisibleStatus(row(true), PACKAGE, [])).toBe('client-pending')
    const loaded: DynamicCordisLivePackage[] = [{
      pluginId: PLUGIN,
      packageId: PACKAGE,
      pluginRunId: RUN,
      name: 'Clock',
      slots: [],
      styleCount: 0,
    }]
    expect(cordisVisibleStatus(row(true), PACKAGE, loaded)).toBe('running')
  })
})
