import { describe, it, expect, afterEach, vi } from 'vitest'

const updateDraft = vi.fn(async (..._args: any[]) => ({
	status: 'saved' as const,
	current_timestamp: '2020-01-01T00:00:00Z'
}))

vi.mock('./gen', () => ({
	DraftService: { updateDraft: (...a: unknown[]) => updateDraft(...(a as [])) }
}))
vi.mock('./gen/core/OpenAPI', () => ({ OpenAPI: { BASE: '' } }))
vi.mock('./localDraftHints.svelte', () => ({ setLocalDraftHint: vi.fn() }))

import { UserDraftDbSyncer } from './userDraftDbSyncer.svelte'

afterEach(() => {
	vi.clearAllMocks()
	UserDraftDbSyncer.autosaveEnabled = true
})

/**
 * Read-only consumers (the chat `diff` tool) flush with `honorAutosaveToggle`
 * and then read `hasUnsavedDisabledChanges` to know the persisted state is
 * stale. Pins the contract pair: a toggle-honoring flush must NOT persist
 * auto-save-off edits (and must keep reporting them), while an explicit flush
 * persists them and clears the signal.
 */
describe('UserDraftDbSyncer toggle-honoring flush', () => {
	it('keeps auto-save-off edits parked and reported; explicit flush clears them', async () => {
		const q = { workspace: 'w', itemKind: 'script' as const, path: 'u/me/toggle_off' }
		UserDraftDbSyncer.autosaveEnabled = false
		await UserDraftDbSyncer.save({ ...q, value: { content: 'x' }, auto: true, canBeDisabled: true })

		await UserDraftDbSyncer.flush(q, { honorAutosaveToggle: true })
		expect(updateDraft).not.toHaveBeenCalled()
		expect(UserDraftDbSyncer.hasUnsavedDisabledChanges(q)).toBe(true)

		await UserDraftDbSyncer.flush(q)
		expect(updateDraft).toHaveBeenCalledTimes(1)
		expect(UserDraftDbSyncer.hasUnsavedDisabledChanges(q)).toBe(false)
	})
})

/** A conflicted save leaves the local payload parked with the conflict
 * recorded while the state reads 'none' — the exact triple the diff tool's
 * unflushed-edits detection reads. Pins that a conflict never looks like a
 * clean sync. */
describe('UserDraftDbSyncer conflict aftermath', () => {
	it('keeps the payload parked and the conflict readable after flush settles', async () => {
		const q = { workspace: 'w', itemKind: 'script' as const, path: 'u/me/conflicted' }
		updateDraft.mockResolvedValueOnce({
			status: 'conflict',
			current_timestamp: '2020-01-02T00:00:00Z'
		})
		await UserDraftDbSyncer.save({ ...q, value: { content: 'mine' }, immediate: true })

		// The triple the diff tool's unflushed detection reads: conflict set,
		// state 'none' (not pending/failed), auto-save signal silent.
		expect(UserDraftDbSyncer.getConflict(q).conflict).toBeDefined()
		expect(UserDraftDbSyncer.getState(q).state).toBe('none')
		expect(UserDraftDbSyncer.hasUnsavedDisabledChanges(q)).toBe(false)
	})
})

/** The diff snapshot cache invalidates through this hook — it must fire for
 * upserts AND deletes, the moment the write lands. */
describe('UserDraftDbSyncer.onAnySaved', () => {
	it('fires for landed upserts and deletes, and unsubscribes cleanly', async () => {
		const q = { workspace: 'w', itemKind: 'script' as const, path: 'u/me/hooked' }
		const events: string[] = []
		const off = UserDraftDbSyncer.onAnySaved((e) => events.push(`${e.itemKind}:${e.path}`))

		await UserDraftDbSyncer.save({ ...q, value: { content: 'x' }, immediate: true })
		await UserDraftDbSyncer.save({ ...q, value: null, immediate: true })
		expect(events).toEqual(['script:u/me/hooked', 'script:u/me/hooked'])

		off()
		await UserDraftDbSyncer.save({ ...q, value: { content: 'y' }, immediate: true })
		expect(events).toHaveLength(2)
	})
})
