/**
 * test/link-paths.test.ts — pure findPathMatches matcher (W1).
 *
 * Node environment (no DOM): the matcher is deliberately DOM-free so it can be
 * unit-tested directly and reused by later diff/preview views.
 */

import { describe, it, expect } from 'vitest'
import { findPathMatches, CODE_EXT } from '../public/link-paths.js'

describe('findPathMatches — file:line paths', () => {
  it('matches a src/app.ts:42 path with correct 1-based range', () => {
    const line = 'see src/app.ts:42 for'
    const matches = findPathMatches(line)
    expect(matches).toHaveLength(1)
    const m = matches[0]!
    expect(m.text).toBe('src/app.ts:42')
    expect(m.path).toBe('src/app.ts')
    expect(m.line).toBe(42)
    expect(m.column).toBeUndefined()
    // 'see ' = 4 chars → startX = 5 (1-based), endX inclusive = 5 + 13 - 1 = 17.
    expect(m.startX).toBe(5)
    expect(m.endX).toBe(17)
    expect(line.slice(m.startX - 1, m.endX)).toBe('src/app.ts:42')
  })

  it('parses :line:col into line + column', () => {
    const matches = findPathMatches('./a/b.tsx:10:5')
    expect(matches).toHaveLength(1)
    const m = matches[0]!
    expect(m.path).toBe('./a/b.tsx')
    expect(m.line).toBe(10)
    expect(m.column).toBe(5)
  })

  it('matches a bare filename with an allowlisted extension (README.md)', () => {
    const matches = findPathMatches('open README.md please')
    expect(matches).toHaveLength(1)
    expect(matches[0]!.path).toBe('README.md')
    expect(matches[0]!.line).toBeUndefined()
  })

  it('matches main.rs:10 via its :line suffix', () => {
    const matches = findPathMatches('at main.rs:10')
    expect(matches).toHaveLength(1)
    expect(matches[0]!.path).toBe('main.rs')
    expect(matches[0]!.line).toBe(10)
  })

  it('matches an absolute path with a leading slash', () => {
    const matches = findPathMatches('Error at /home/u/app.ts:42 now')
    expect(matches).toHaveLength(1)
    const m = matches[0]!
    expect(m.path).toBe('/home/u/app.ts')
    expect(m.line).toBe(42)
  })
})

describe('findPathMatches — non-paths are rejected', () => {
  it('does not match a bare domain (example.com)', () => {
    expect(findPathMatches('visit example.com today')).toHaveLength(0)
  })

  it('does not match a version string (v1.2.3)', () => {
    expect(findPathMatches('version v1.2.3 released')).toHaveLength(0)
  })

  it('does not match a foo.bar token (unknown extension, no slash/line)', () => {
    expect(findPathMatches('the foo.bar thing')).toHaveLength(0)
  })

  it('does not match a bare timestamp (12:34)', () => {
    expect(findPathMatches('at 12:34 today')).toHaveLength(0)
  })
})

describe('findPathMatches — URL guard', () => {
  it('does not double-link the path tail of an http URL (WebLinksAddon owns it)', () => {
    expect(findPathMatches('https://host/path.html')).toHaveLength(0)
  })

  it('does not match a path glued to a URL host (a.ts inside http://x/a.ts)', () => {
    expect(findPathMatches('http://example.com/src/a.ts')).toHaveLength(0)
  })
})

describe('findPathMatches — multiple + relative paths', () => {
  it('returns two disjoint matches for two paths on one line', () => {
    const matches = findPathMatches('edit src/a.ts:1 and lib/b.rs:2')
    expect(matches).toHaveLength(2)
    expect(matches[0]!.path).toBe('src/a.ts')
    expect(matches[1]!.path).toBe('lib/b.rs')
    // Ranges are disjoint and ordered.
    expect(matches[0]!.endX).toBeLessThan(matches[1]!.startX)
  })

  it('keeps a ../ prefix in the path portion', () => {
    const matches = findPathMatches('see ../lib/util.ts:7')
    expect(matches).toHaveLength(1)
    expect(matches[0]!.path).toBe('../lib/util.ts')
    expect(matches[0]!.line).toBe(7)
  })

  it('matches a nested config.test.ts filename (multi-dot) with the final extension', () => {
    const matches = findPathMatches('run test/config.test.ts')
    expect(matches).toHaveLength(1)
    expect(matches[0]!.path).toBe('test/config.test.ts')
  })

  it('returns [] for a line with no paths', () => {
    expect(findPathMatches('just some plain words here')).toEqual([])
  })
})

describe('CODE_EXT allowlist', () => {
  it('contains common code extensions and not arbitrary ones', () => {
    expect(CODE_EXT.has('ts')).toBe(true)
    expect(CODE_EXT.has('md')).toBe(true)
    expect(CODE_EXT.has('rs')).toBe(true)
    expect(CODE_EXT.has('com')).toBe(false)
    expect(CODE_EXT.has('bar')).toBe(false)
  })
})
