/**
 * Tests for the local backend through the `ctx.fs` provider seam: stat, whole-
 * file/streamed text reads, atomic guarded writes (createIfAbsent /
 * replaceIfVersion), version-guarded literal edits, concurrency races, symlink
 * identity, and HMR/disposal. Read WINDOWING is policy and lives in
 * `dsh-fs-policy`, so it is not exercised here.
 */

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { mkdir, mkdtemp, readFile, realpath, rm, stat, symlink, unlink, utimes, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { Context } from 'cordis'
import { LocalFileSystem } from '@deepseek-ai/dsh-fs-local'
import { FsVersion } from '@deepseek-ai/dsh-fs'
import type { FsTarget } from '@deepseek-ai/dsh-fs'

let dir: string
let ctx: Context
let fs: LocalFileSystem
let fiber: Awaited<ReturnType<Context['plugin']>>

beforeEach(async () => {
  dir = await mkdtemp(join(tmpdir(), 'dsh-fs-'))
  ctx = new Context()
  fiber = await ctx.plugin(LocalFileSystem, { cwd: dir })
  fs = ctx.fs as LocalFileSystem
})
afterEach(async () => {
  await fiber.dispose()
  await rm(dir, { recursive: true, force: true })
})

function lockCount(localFs: LocalFileSystem): number {
  return (localFs as unknown as { locks: Map<string, Promise<unknown>> }).locks.size
}

/** The version the backend currently reports for a resolved target. */
async function versionOf(target: FsTarget): Promise<FsVersion> {
  const info = await fs.stat(target)
  if (!info) throw new Error('expected target to exist')
  return info.version
}

describe('registration', () => {
  it('registers LocalFileSystem as ctx.fs with a default cwd', async () => {
    const bare = new Context()
    const bareFiber = await bare.plugin(LocalFileSystem)
    expect((bare.fs as LocalFileSystem).config.cwd).toBe(process.cwd())
    await bareFiber.dispose()
  })
})

describe('resolve', () => {
  it('resolves a relative path against opts.cwd, not config.cwd', async () => {
    // config.cwd is `dir`; a call supplying a DIFFERENT cwd bases the relative
    // path there (the per-session-workspace seam — mirrors tool-bash workdir).
    const other = await mkdtemp(join(tmpdir(), 'dsh-fs-other-'))
    try {
      await writeFile(join(other, 'x.txt'), 'in other')
      const viaOther = await fs.resolve('x.txt', { cwd: other })
      expect(await fs.readText(viaOther)).toBe('in other')
      // Same relative path with no opts falls back to config.cwd (= dir), where
      // x.txt does not exist.
      await expect(fs.readText(await fs.resolve('x.txt'))).rejects.toMatchObject({ code: 'FS_NOT_FOUND' })
    } finally {
      await rm(other, { recursive: true, force: true })
    }
  })

  it('ignores opts.cwd for an ABSOLUTE path', async () => {
    await writeFile(join(dir, 'abs.txt'), 'absolute')
    const target = await fs.resolve(join(dir, 'abs.txt'), { cwd: '/nonexistent-base' })
    expect(await fs.readText(target)).toBe('absolute')
  })

  it('honors a pre-aborted signal', async () => {
    await expect(fs.resolve('a.txt', { signal: AbortSignal.abort() })).rejects.toMatchObject({ code: 'FS_ABORTED' })
  })

  it('honors a signal aborted while resolution is in flight', async () => {
    const controller = new AbortController()
    const pending = fs.resolve('a.txt', { signal: controller.signal })
    controller.abort()

    await expect(pending).rejects.toMatchObject({ code: 'FS_ABORTED' })
  })
})

describe('stat', () => {
  it('returns file metadata, directory type, and undefined for absent', async () => {
    await writeFile(join(dir, 'a.txt'), 'hello')
    const fileInfo = await fs.stat(await fs.resolve('a.txt'))
    expect(fileInfo?.type).toBe('file')
    expect(fileInfo?.size).toBe(5)
    expect(typeof fileInfo?.version).toBe('string')

    expect((await fs.stat(await fs.resolve('.')))?.type).toBe('directory')
    expect(await fs.stat(await fs.resolve('missing.txt'))).toBeUndefined()
  })

  it('changes version after a same-size rewrite even when mtime is restored', async () => {
    const path = join(dir, 'same-size.txt')
    await writeFile(path, 'first')
    const target = await fs.resolve(path)
    const beforeInfo = await stat(path)
    const beforeVersion = await versionOf(target)

    await fs.writeText(target, 'other')
    await utimes(path, beforeInfo.atime, beforeInfo.mtime)

    expect((await stat(path)).size).toBe(beforeInfo.size)
    expect(await versionOf(target)).not.toBe(beforeVersion)
  })

  it('honors a pre-aborted signal', async () => {
    await expect(fs.stat(await fs.resolve('a.txt'), AbortSignal.abort())).rejects.toMatchObject({ code: 'FS_ABORTED' })
  })
})

describe('lstat', () => {
  it('reports path metadata without following the final symlink component', async () => {
    await writeFile(join(dir, 'real.txt'), 'hello')
    await symlink(join(dir, 'real.txt'), join(dir, 'link.txt'))

    expect((await fs.lstat('real.txt'))?.type).toBe('file')
    expect((await fs.lstat('link.txt'))?.type).toBe('symlink')
    expect(await fs.lstat('missing.txt')).toBeUndefined()
  })

  it('resolves relative paths against opts.cwd and honors a pre-aborted signal', async () => {
    const other = await mkdtemp(join(tmpdir(), 'dsh-fs-other-'))
    try {
      await writeFile(join(other, 'x.txt'), 'in other')
      expect((await fs.lstat('x.txt', { cwd: other }))?.type).toBe('file')
      await expect(fs.lstat('x.txt', { cwd: other }, AbortSignal.abort())).rejects.toMatchObject({ code: 'FS_ABORTED' })
      await expect(fs.lstat('   ')).rejects.toMatchObject({ code: 'FS_NOT_FOUND' })
    } finally {
      await rm(other, { recursive: true, force: true })
    }
  })
})

describe('metadata cancellation', () => {
  it('rejects stat and lstat when their signals abort while the metadata probes are in flight', async () => {
    await writeFile(join(dir, 'slow.txt'), 'hello')
    const statStarted = Promise.withResolvers<undefined>()
    const statRelease = Promise.withResolvers<undefined>()
    const lstatStarted = Promise.withResolvers<undefined>()
    const lstatRelease = Promise.withResolvers<undefined>()
    let isolatedCtx: Context | undefined
    vi.resetModules()
    vi.doMock('node:fs/promises', async (importOriginal) => {
      const actual = await importOriginal<typeof import('node:fs/promises')>()
      return {
        ...actual,
        async stat(path: string) {
          statStarted.resolve(undefined)
          await statRelease.promise
          return actual.stat(path, { bigint: true })
        },
        async lstat(path: string) {
          lstatStarted.resolve(undefined)
          await lstatRelease.promise
          return actual.lstat(path, { bigint: true })
        },
      }
    })

    try {
      const { LocalFileSystem: IsolatedLocalFileSystem } = await import('../src/index.ts')
      isolatedCtx = new Context()
      await isolatedCtx.plugin(IsolatedLocalFileSystem, { cwd: dir })
      const isolatedFs = isolatedCtx.fs as InstanceType<typeof IsolatedLocalFileSystem>
      const target = await isolatedFs.resolve('slow.txt')
      const statController = new AbortController()
      const lstatController = new AbortController()
      const pendingStat = isolatedFs.stat(target, statController.signal)
      const pendingLstat = isolatedFs.lstat('slow.txt', undefined, lstatController.signal)

      await Promise.all([statStarted.promise, lstatStarted.promise])
      statController.abort()
      lstatController.abort()
      const statRejected = expect(pendingStat).rejects.toMatchObject({ code: 'FS_ABORTED' })
      const lstatRejected = expect(pendingLstat).rejects.toMatchObject({ code: 'FS_ABORTED' })
      statRelease.resolve(undefined)
      lstatRelease.resolve(undefined)

      await Promise.all([statRejected, lstatRejected])
    } finally {
      statRelease.resolve(undefined)
      lstatRelease.resolve(undefined)
      await isolatedCtx?.fiber.dispose()
      vi.doUnmock('node:fs/promises')
      vi.resetModules()
    }
  })
})

describe('readText / streamText', () => {
  it('reads whole-file text', async () => {
    await writeFile(join(dir, 'a.txt'), 'one\ntwo\nthree')
    expect(await fs.readText(await fs.resolve('a.txt'))).toBe('one\ntwo\nthree')
  })

  it('streams the same text', async () => {
    await writeFile(join(dir, 'a.txt'), 'one\ntwo\nthree')
    const target = await fs.resolve('a.txt')
    let streamed = ''
    for await (const chunk of await fs.streamText(target)) streamed += chunk
    expect(streamed).toBe('one\ntwo\nthree')
  })

  it('rejects a missing file, a directory, binary, and invalid UTF-8', async () => {
    await expect(fs.readText(await fs.resolve('nope'))).rejects.toMatchObject({ code: 'FS_NOT_FOUND' })
    await expect(fs.readText(await fs.resolve('.'))).rejects.toMatchObject({ code: 'FS_NOT_REGULAR_FILE' })

    await writeFile(join(dir, 'bin'), Buffer.from([0x68, 0x00, 0x69]))
    await expect(fs.readText(await fs.resolve('bin'))).rejects.toMatchObject({ code: 'FS_NOT_TEXT' })

    await writeFile(join(dir, 'bad'), Buffer.from([0x68, 0xff, 0x69]))
    await expect(fs.readText(await fs.resolve('bad'))).rejects.toMatchObject({ code: 'FS_NOT_TEXT' })
  })
})

describe('listDir', () => {
  it('lists files and directories in stable name order with resolved child targets', async () => {
    await mkdir(join(dir, 'skills', 'dir-skill'), { recursive: true })
    await writeFile(join(dir, 'skills', 'zeta.md'), 'zeta')
    await writeFile(join(dir, 'skills', 'alpha.md'), 'alpha')
    await symlink(join(dir, 'skills', 'missing-target'), join(dir, 'skills', 'broken-link'))

    const entries = await fs.listDir(await fs.resolve('skills'))
    expect(entries.map(entry => [entry.name, entry.type])).toEqual([
      ['alpha.md', 'file'],
      ['broken-link', 'other'],
      ['dir-skill', 'directory'],
      ['zeta.md', 'file'],
    ])
    expect(entries.map(entry => entry.target.displayPath)).toEqual([
      join(dir, 'skills', 'alpha.md'),
      join(dir, 'skills', 'broken-link'),
      join(dir, 'skills', 'dir-skill'),
      join(dir, 'skills', 'zeta.md'),
    ])
    const materializedEntries = entries.filter(entry => entry.version !== undefined)
    expect(materializedEntries.map(entry => entry.target.targetKey))
      .toEqual(await Promise.all(materializedEntries.map(entry => realpath(entry.target.displayPath))))
    expect(entries.find(entry => entry.name === 'alpha.md')?.size).toBe(5)
    expect(typeof entries.find(entry => entry.name === 'alpha.md')?.version).toBe('string')
    expect(entries.find(entry => entry.name === 'broken-link')?.version).toBeUndefined()
    expect(entries.find(entry => entry.name === 'dir-skill')?.size).toBeUndefined()
  })

  it('reports a missing directory as FS_NOT_FOUND', async () => {
    await expect(fs.listDir(await fs.resolve('missing'))).rejects.toMatchObject({ code: 'FS_NOT_FOUND' })
  })

  it('reports a file target as FS_NOT_DIRECTORY', async () => {
    await writeFile(join(dir, 'a.txt'), 'text')
    await expect(fs.listDir(await fs.resolve('a.txt'))).rejects.toMatchObject({ code: 'FS_NOT_DIRECTORY' })
  })

  it('honors a pre-aborted signal', async () => {
    await mkdir(join(dir, 'skills'), { recursive: true })
    await expect(fs.listDir(await fs.resolve('skills'), AbortSignal.abort())).rejects.toMatchObject({ code: 'FS_ABORTED' })
  })
})

describe('writeText', () => {
  it('createIfAbsent creates a new file', async () => {
    const target = await fs.resolve('new.txt')
    const outcome = await fs.writeText(target, 'fresh', { kind: 'createIfAbsent' })
    expect(outcome.operation).toBe('create')
    expect(await readFile(join(dir, 'new.txt'), 'utf8')).toBe('fresh')
  })

  it('createIfAbsent rejects an existing file as FS_NOT_OBSERVED', async () => {
    await writeFile(join(dir, 'a.txt'), 'old')
    const target = await fs.resolve('a.txt')
    await expect(fs.writeText(target, 'new', { kind: 'createIfAbsent' }))
      .rejects.toMatchObject({ code: 'FS_NOT_OBSERVED' })
    expect(await readFile(join(dir, 'a.txt'), 'utf8')).toBe('old')
  })

  it('replaceIfVersion replaces when the version matches', async () => {
    await writeFile(join(dir, 'a.txt'), 'old')
    const target = await fs.resolve('a.txt')
    const outcome = await fs.writeText(target, 'new', { kind: 'replaceIfVersion', version: await versionOf(target) })
    expect(outcome.operation).toBe('update')
    expect(await readFile(join(dir, 'a.txt'), 'utf8')).toBe('new')
  })

  it('replaceIfVersion rejects a stale version', async () => {
    await writeFile(join(dir, 'a.txt'), 'v1')
    const target = await fs.resolve('a.txt')
    const stale = await versionOf(target)
    await writeFile(join(dir, 'a.txt'), 'changed-externally')
    await expect(fs.writeText(target, 'v2', { kind: 'replaceIfVersion', version: stale }))
      .rejects.toMatchObject({ code: 'FS_STALE_VERSION' })
  })

  it('replaceIfVersion rejects a deleted target as stale, without recreating it', async () => {
    const path = join(dir, 'a.txt')
    await writeFile(path, 'v1')
    const target = await fs.resolve('a.txt')
    const version = await versionOf(target)
    await unlink(path)
    await expect(fs.writeText(target, 'v2', { kind: 'replaceIfVersion', version }))
      .rejects.toMatchObject({ code: 'FS_STALE_VERSION' })
    await expect(stat(path)).rejects.toMatchObject({ code: 'ENOENT' })
  })

  it('rejects writing onto a directory', async () => {
    const target = await fs.resolve('.')
    await expect(fs.writeText(target, 'x', { kind: 'createIfAbsent' }))
      .rejects.toMatchObject({ code: 'FS_NOT_REGULAR_FILE' })
  })

  it('unconditionally creates a new file with no expectation (bare provider)', async () => {
    const target = await fs.resolve('new.txt')
    const outcome = await fs.writeText(target, 'fresh')
    expect(outcome.operation).toBe('create')
    expect(await readFile(join(dir, 'new.txt'), 'utf8')).toBe('fresh')
  })

  it('unconditionally OVERWRITES an existing file with no expectation (bare provider)', async () => {
    await writeFile(join(dir, 'a.txt'), 'old')
    const target = await fs.resolve('a.txt')
    const outcome = await fs.writeText(target, 'clobbered')
    expect(outcome.operation).toBe('update')
    expect(await readFile(join(dir, 'a.txt'), 'utf8')).toBe('clobbered')
  })

  it('rejects writing onto a directory even with no expectation', async () => {
    const target = await fs.resolve('.')
    await expect(fs.writeText(target, 'x')).rejects.toMatchObject({ code: 'FS_NOT_REGULAR_FILE' })
  })

  it('a create reports before:null and after = the written content (no prior file)', async () => {
    const target = await fs.resolve('new.txt')
    const outcome = await fs.writeText(target, 'fresh')
    expect(outcome.before).toBeNull()
    expect(outcome.after).toBe('fresh')
  })

  it('an overwrite reports before = the OLD content and after = the new content', async () => {
    await writeFile(join(dir, 'a.txt'), 'old body')
    const target = await fs.resolve('a.txt')
    const outcome = await fs.writeText(target, 'new body')
    expect(outcome.before).toBe('old body')
    expect(outcome.after).toBe('new body')
  })

  it('an overwrite returns LF-normalized before AND after (a CRLF rewrite is not every-line-changed)', async () => {
    // The applied-hunk diff bases on `before`/`after`; if `after` kept CRLF while
    // `before` is LF-normalized, a CRLF rewrite would read as every line changed.
    // Both sides are LF so only the genuinely-changed line diffs.
    await writeFile(join(dir, 'a.txt'), 'a\r\nb\r\nc\r\n')
    const target = await fs.resolve('a.txt')
    const outcome = await fs.writeText(target, 'a\r\nB\r\nc\r\n')
    expect(outcome.before).toBe('a\nb\nc\n')
    expect(outcome.after).toBe('a\nB\nc\n')
  })

  it('an overwrite of a BINARY prior file reports before:null (undiffable), still succeeds', async () => {
    await writeFile(join(dir, 'a.bin'), Buffer.from([0x00, 0x01, 0x02]))
    const target = await fs.resolve('a.bin')
    const outcome = await fs.writeText(target, 'now text')
    expect(outcome.operation).toBe('update')
    expect(outcome.before).toBeNull()
    expect(outcome.after).toBe('now text')
  })

  it('an overwrite of an INVALID-UTF-8 (non-NUL) prior file reports before:null, still succeeds', async () => {
    // 0xff is never valid UTF-8 but is not a NUL, so it exercises the decoder's
    // fatal-throw path (not the NUL-scan short-circuit): an undiffable prior file
    // still yields a successful write with no before-content basis.
    await writeFile(join(dir, 'a.bin'), Buffer.from([0x68, 0xff, 0x69]))
    const target = await fs.resolve('a.bin')
    const outcome = await fs.writeText(target, 'now valid')
    expect(outcome.operation).toBe('update')
    expect(outcome.before).toBeNull()
    expect(outcome.after).toBe('now valid')
  })

  it('releases per-target mutation locks after success and failure', async () => {
    const target = await fs.resolve('a.txt')
    await fs.writeText(target, 'created', { kind: 'createIfAbsent' })
    expect(lockCount(fs)).toBe(0)
    await expect(fs.writeText(target, 'again', { kind: 'createIfAbsent' }))
      .rejects.toMatchObject({ code: 'FS_NOT_OBSERVED' })
    expect(lockCount(fs)).toBe(0)
  })

  it('replaceIfVersion returns the post-write version (matches a fresh stat)', async () => {
    await writeFile(join(dir, 'a.txt'), 'v1')
    const target = await fs.resolve('a.txt')
    const before = await versionOf(target)
    const outcome = await fs.writeText(target, 'a much longer replacement body', { kind: 'replaceIfVersion', version: before })
    expect(outcome.version).not.toBe(before)
    expect(outcome.version).toBe(await versionOf(target))
  })

  it('honors a pre-aborted signal without creating the file', async () => {
    const target = await fs.resolve('aborted.txt')
    await expect(fs.writeText(target, 'x', undefined, AbortSignal.abort()))
      .rejects.toMatchObject({ code: 'FS_ABORTED' })
    await expect(stat(join(dir, 'aborted.txt'))).rejects.toMatchObject({ code: 'ENOENT' })
    expect(lockCount(fs)).toBe(0)
  })

  it('two concurrent guarded writes: one updates, the other is rejected as stale', async () => {
    await writeFile(join(dir, 'a.txt'), 'base')
    const target = await fs.resolve('a.txt')
    const version = await versionOf(target)
    const results = await Promise.allSettled([
      fs.writeText(target, 'one', { kind: 'replaceIfVersion', version }),
      fs.writeText(target, 'two', { kind: 'replaceIfVersion', version }),
    ])
    expect(results.filter(r => r.status === 'fulfilled')).toHaveLength(1)
    const rejected = results.filter(r => r.status === 'rejected')
    expect(rejected).toHaveLength(1)
    expect((rejected[0] as PromiseRejectedResult).reason).toMatchObject({ code: 'FS_STALE_VERSION' })
    expect(lockCount(fs)).toBe(0)
  })
})

describe('editText', () => {
  it('applies a literal edit at the matching version', async () => {
    await writeFile(join(dir, 'a.txt'), 'hello world')
    const target = await fs.resolve('a.txt')
    const outcome = await fs.editText(target, { oldString: 'world', newString: 'there', replaceAll: false }, { version: await versionOf(target) })
    expect(outcome.after).toBe('hello there')
    expect(await readFile(join(dir, 'a.txt'), 'utf8')).toBe('hello there')
  })

  it('reports before/after content (the applied-hunk basis), LF-normalized', async () => {
    await writeFile(join(dir, 'a.txt'), 'a\r\nOLD\r\nb\r\n')
    const target = await fs.resolve('a.txt')
    const outcome = await fs.editText(target, { oldString: 'OLD', newString: 'NEW', replaceAll: false })
    expect(outcome.before).toBe('a\nOLD\nb\n')
    expect(outcome.after).toBe('a\nNEW\nb\n')
    // The written file keeps the original CRLF endings (before/after are the
    // LF-normalized diff basis, not the on-disk bytes).
    expect(await readFile(join(dir, 'a.txt'), 'utf8')).toBe('a\r\nNEW\r\nb\r\n')
  })

  it('checks the stale version BEFORE literal matching', async () => {
    await writeFile(join(dir, 'a.txt'), 'hello world')
    const target = await fs.resolve('a.txt')
    const stale = await versionOf(target)
    // Change the file so 'world' is gone — a stale edit must report STALE, not NOT_FOUND.
    await writeFile(join(dir, 'a.txt'), 'goodbye')
    await expect(fs.editText(target, { oldString: 'world', newString: 'there', replaceAll: false }, { version: stale }))
      .rejects.toMatchObject({ code: 'FS_STALE_VERSION' })
  })

  it('unconditionally edits the current content with no expectation (bare provider)', async () => {
    await writeFile(join(dir, 'a.txt'), 'hello world')
    const target = await fs.resolve('a.txt')
    // No version guard: any current content is edited, regardless of version.
    const outcome = await fs.editText(target, { oldString: 'world', newString: 'there', replaceAll: false })
    expect(outcome.after).toBe('hello there')
    expect(await readFile(join(dir, 'a.txt'), 'utf8')).toBe('hello there')
  })

  it('reports a missing target as FS_STALE_VERSION even with no expectation (bare provider)', async () => {
    const target = await fs.resolve('missing.txt')
    await expect(fs.editText(target, { oldString: 'a', newString: 'b', replaceAll: false }))
      .rejects.toMatchObject({ code: 'FS_STALE_VERSION' })
  })

  it('still reports literal-match codes with no expectation (FS_EDIT_NOT_FOUND, unrelated to freshness)', async () => {
    await writeFile(join(dir, 'a.txt'), 'hello world')
    const target = await fs.resolve('a.txt')
    await expect(fs.editText(target, { oldString: 'absent', newString: 'x', replaceAll: false }))
      .rejects.toMatchObject({ code: 'FS_EDIT_NOT_FOUND' })
  })

  it('rejects a deleted target as stale (before matching)', async () => {
    await writeFile(join(dir, 'a.txt'), 'hello')
    const target = await fs.resolve('a.txt')
    const version = await versionOf(target)
    await unlink(join(dir, 'a.txt'))
    await expect(fs.editText(target, { oldString: 'hello', newString: 'bye', replaceAll: false }, { version }))
      .rejects.toMatchObject({ code: 'FS_STALE_VERSION' })
  })

  it('rejects a non-regular target', async () => {
    const target = await fs.resolve('.')
    await expect(fs.editText(target, { oldString: 'a', newString: 'b', replaceAll: false }, { version: FsVersion('v') }))
      .rejects.toMatchObject({ code: 'FS_NOT_REGULAR_FILE' })
  })

  it('rejects zero matches and ambiguous matches at the right version', async () => {
    await writeFile(join(dir, 'a.txt'), 'a a a')
    const target = await fs.resolve('a.txt')
    const version = await versionOf(target)
    await expect(fs.editText(target, { oldString: 'z', newString: 'X', replaceAll: false }, { version }))
      .rejects.toMatchObject({ code: 'FS_EDIT_NOT_FOUND' })
    await expect(fs.editText(target, { oldString: 'a', newString: 'X', replaceAll: false }, { version }))
      .rejects.toMatchObject({ code: 'FS_AMBIGUOUS_EDIT' })
  })

  it('replaces all matches with replaceAll', async () => {
    await writeFile(join(dir, 'a.txt'), 'a a a')
    const target = await fs.resolve('a.txt')
    const outcome = await fs.editText(target, { oldString: 'a', newString: 'b', replaceAll: true }, { version: await versionOf(target) })
    expect(outcome.after).toBe('b b b')
    expect(await readFile(join(dir, 'a.txt'), 'utf8')).toBe('b b b')
  })

  it('rejects invalid UTF-8 without rewriting the file', async () => {
    const path = join(dir, 'bad.txt')
    const bytes = Buffer.from([0x68, 0xff, 0x69])
    await writeFile(path, bytes)
    const target = await fs.resolve('bad.txt')
    const version = await versionOf(target)
    await expect(fs.editText(target, { oldString: 'h', newString: 'H', replaceAll: false }, { version }))
      .rejects.toMatchObject({ code: 'FS_NOT_TEXT' })
    expect(await readFile(path)).toEqual(bytes)
  })

  it('two concurrent edits: one wins, the other is rejected as stale', async () => {
    await writeFile(join(dir, 'a.txt'), 'base')
    const target = await fs.resolve('a.txt')
    const version = await versionOf(target)
    const results = await Promise.allSettled([
      fs.editText(target, { oldString: 'base', newString: 'one', replaceAll: false }, { version }),
      fs.editText(target, { oldString: 'base', newString: 'two', replaceAll: false }, { version }),
    ])
    expect(results.filter(r => r.status === 'fulfilled')).toHaveLength(1)
    const rejected = results.filter(r => r.status === 'rejected')
    expect(rejected).toHaveLength(1)
    expect((rejected[0] as PromiseRejectedResult).reason).toMatchObject({ code: 'FS_STALE_VERSION' })
    expect(lockCount(fs)).toBe(0)
  })

  it('honors a pre-aborted signal without rewriting the file', async () => {
    await writeFile(join(dir, 'a.txt'), 'keep')
    const target = await fs.resolve('a.txt')
    await expect(fs.editText(target, { oldString: 'keep', newString: 'x', replaceAll: false }, undefined, AbortSignal.abort()))
      .rejects.toMatchObject({ code: 'FS_ABORTED' })
    expect(await readFile(join(dir, 'a.txt'), 'utf8')).toBe('keep')
    expect(lockCount(fs)).toBe(0)
  })

  it('a successful edit refreshes the version so an immediate follow-up edit proceeds', async () => {
    await writeFile(join(dir, 'a.txt'), 'one two')
    const target = await fs.resolve('a.txt')
    const first = await fs.editText(target, { oldString: 'one', newString: 'ONE', replaceAll: false }, { version: await versionOf(target) })
    // The version the first edit returned is a valid guard for a second edit —
    // no intervening re-stat needed.
    const second = await fs.editText(target, { oldString: 'two', newString: 'TWO', replaceAll: false }, { version: first.version })
    expect(second.after).toBe('ONE TWO')
    expect(await readFile(join(dir, 'a.txt'), 'utf8')).toBe('ONE TWO')
  })

  it('concurrent write vs edit at the same version: one wins, the other is stale', async () => {
    await writeFile(join(dir, 'a.txt'), 'base')
    const target = await fs.resolve('a.txt')
    const version = await versionOf(target)
    const results = await Promise.allSettled([
      fs.writeText(target, 'written', { kind: 'replaceIfVersion', version }),
      fs.editText(target, { oldString: 'base', newString: 'edited', replaceAll: false }, { version }),
    ])
    expect(results.filter(r => r.status === 'fulfilled')).toHaveLength(1)
    const rejected = results.filter(r => r.status === 'rejected')
    expect(rejected).toHaveLength(1)
    expect((rejected[0] as PromiseRejectedResult).reason).toMatchObject({ code: 'FS_STALE_VERSION' })
    expect(lockCount(fs)).toBe(0)
  })
})

describe('symlink targetKey identity', () => {
  it('two paths to the same file via a symlink share one version and write the real target', async () => {
    await writeFile(join(dir, 'real.txt'), 'hello')
    await symlink(join(dir, 'real.txt'), join(dir, 'link.txt'))
    const viaReal = await fs.resolve('real.txt')
    const viaLink = await fs.resolve('link.txt')
    expect(viaLink.targetKey).toBe(viaReal.targetKey)

    const version = await versionOf(viaReal)
    await fs.editText(viaLink, { oldString: 'hello', newString: 'bye', replaceAll: false }, { version })
    expect(await readFile(join(dir, 'real.txt'), 'utf8')).toBe('bye') // link preserved
  })

  it('a stale change is detected across both paths', async () => {
    await writeFile(join(dir, 'real.txt'), 'hello')
    await symlink(join(dir, 'real.txt'), join(dir, 'link.txt'))
    const viaReal = await fs.resolve('real.txt')
    const stale = await versionOf(viaReal)
    await writeFile(join(dir, 'real.txt'), 'changed')
    const viaLink = await fs.resolve('link.txt')
    await expect(fs.editText(viaLink, { oldString: 'hello', newString: 'bye', replaceAll: false }, { version: stale }))
      .rejects.toMatchObject({ code: 'FS_STALE_VERSION' })
  })
})

describe('HMR / disposal', () => {
  it('disposing the fiber withdraws ctx.fs', async () => {
    const local = new Context()
    const localFiber = await local.plugin(LocalFileSystem, { cwd: dir })
    expect(local.fs).toBeDefined()
    await localFiber.dispose()
    expect(local.fs).toBeUndefined()
  })
})
