#!/usr/bin/env python3
"""echo_quality.py — M2: keep the graph (which recall depends on) clean.
[v1.0 SCAFFOLD — helpers implemented; integration into echo_links/echo_index is the merge step]

Two graph-polluting bugs today:
  1. Auto-link fires on any indexed entity whose name/alias is >=3 chars and word-matches
     the body (echo_ops.py:236). A concept titled "API" or a 2-3 char alias links to every
     note that happens to mention the word -> noisy, wrong edges.
  2. slugify truncates to 40 chars (echo_index.py:58) with NO collision check, so two
     distinct long titles can resolve to the same slug -> same path -> silent merge.

This module supplies the two guards. Integration:
  * echo_ops auto-link loop -> gate each candidate on is_confident_link(name, body)
  * echo_index.derive_path  -> route the slug through safe_slug(existing_slugs, slug)
"""
from __future__ import annotations

import re

# Tokens too short or too common to be a confident entity reference on their own.
MIN_NAME_LEN = 4
_COMMON = frozenset(
    "api app data note task user team work code test main repo file plan goal item "
    "the and for with from".split()
)


def is_confident_link(name: str, body: str) -> bool:
    """True only if `name` is a strong enough signal to auto-link on its appearance in
    `body`. Rejects short names, common words, and non-matches. Multi-word names are
    trusted at lower length (a two-word phrase is rarely incidental)."""
    n = (name or "").strip()
    if not n:
        return False
    multiword = len(n.split()) > 1
    if not multiword:
        if len(n) < MIN_NAME_LEN or n.lower() in _COMMON:
            return False
    return re.search(rf"\b{re.escape(n)}\b", body or "", re.IGNORECASE) is not None


def safe_slug(existing_slugs, slug: str, max_len: int = 40) -> str:
    """Return `slug` if free, else a disambiguated `slug-2`, `slug-3`, ... that still
    fits `max_len` and does not collide with anything in `existing_slugs`."""
    existing = set(existing_slugs or ())
    if slug not in existing:
        return slug
    n = 2
    while True:
        suffix = f"-{n}"
        base = slug[: max_len - len(suffix)] if len(slug) + len(suffix) > max_len else slug
        cand = f"{base}{suffix}"
        if cand not in existing:
            return cand
        n += 1
