/**
 * public/link-paths.ts — pure file-path matcher for the terminal link provider (W1).
 *
 * Finds `src/app.ts:42`, `README.md`, `../lib/util.rs:10:5` etc. in a line of
 * terminal text so the terminal-session link provider can turn them into
 * clickable "open in editor" links. Deliberately DOM-free / xterm-free so it is
 * unit-testable in node and reusable by the diff/approval-preview views later.
 *
 * A candidate token is: an optional `./`, `../` or `dir/…/` prefix, a filename
 * with a dot-extension, and an optional `:line` / `:line:col` suffix. A candidate
 * is only linked when it is *probably* a real path — it has a `/` separator, OR a
 * `:line` suffix, OR its extension is in the CODE_EXT allowlist. This links code
 * paths while ignoring `example.com`, `v1.2.3`, `foo.bar`, and `12:34`.
 */

/** File extensions that mark a bare (slash-less, line-less) token as a code path. */
export const CODE_EXT: ReadonlySet<string> = new Set([
  'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs',
  'py', 'go', 'rs', 'rb', 'java', 'kt',
  'c', 'h', 'cpp', 'hpp', 'cc', 'cs', 'php', 'swift',
  'css', 'scss', 'html', 'json', 'yaml', 'yml', 'toml',
  'md', 'txt', 'sh', 'sql', 'vue', 'svelte',
])

export interface PathMatch {
  /** Exact matched substring, incl. any `:line[:col]` suffix (e.g. "src/app.ts:42"). */
  text: string
  /** The file-path portion only, without the line/column suffix (e.g. "src/app.ts"). */
  path: string
  /** 1-based line number, when a `:line` suffix was present. */
  line?: number
  /** 1-based column, when a `:line:col` suffix was present. */
  column?: number
  /** 1-based column of the first char (maps to an xterm range.start.x). */
  startX: number
  /** 1-based column of the last char, inclusive (maps to an xterm range.end.x). */
  endX: number
}

// A path candidate. The leading negative lookbehind rejects tokens glued to a
// preceding word char, '.', '/', ':', '@' or '-' — this keeps the matcher from
// grabbing the `path.html` tail of a `https://host/path.html` URL (which the
// WebLinksAddon owns) or the middle of a larger identifier. The optional prefix
// admits an absolute `/`, a `./` or a `../` before the dir segments.
//   group 1 = the path (prefix + dirs + filename.ext), no line/col
//   group 2 = line digits (optional)
//   group 3 = column digits (optional)
const PATH_RE =
  /(?<![\w./:@-])((?:\/|\.{1,2}\/)?(?:[\w.-]+\/)*[\w.-]*\.[A-Za-z0-9]+)(?::(\d+)(?::(\d+))?)?/g

/** Lower-cased extension of a path (after the last dot), or null when there is none. */
function extensionOf(pathPart: string): string | null {
  const dot = pathPart.lastIndexOf('.')
  if (dot < 0 || dot === pathPart.length - 1) return null
  return pathPart.slice(dot + 1).toLowerCase()
}

/** A candidate is a real path iff it has a dir separator, a :line suffix, or a code extension. */
function isLikelyPath(pathPart: string, hasLine: boolean): boolean {
  if (pathPart.includes('/')) return true
  if (hasLine) return true
  const ext = extensionOf(pathPart)
  return ext !== null && CODE_EXT.has(ext)
}

/**
 * Find every clickable path in a single line of terminal text. Ranges are
 * 1-based columns to match xterm's IBufferRange (startX = index+1, endX inclusive).
 */
export function findPathMatches(lineText: string): PathMatch[] {
  const matches: PathMatch[] = []
  PATH_RE.lastIndex = 0
  let m: RegExpExecArray | null
  while ((m = PATH_RE.exec(lineText)) !== null) {
    const full = m[0]
    const pathPart = m[1] as string
    const hasLine = m[2] !== undefined
    if (!isLikelyPath(pathPart, hasLine)) continue

    const startIndex = m.index
    matches.push({
      text: full,
      path: pathPart,
      ...(hasLine ? { line: Number(m[2]) } : {}),
      ...(m[3] !== undefined ? { column: Number(m[3]) } : {}),
      startX: startIndex + 1,
      endX: startIndex + full.length,
    })
  }
  return matches
}
