#!/usr/bin/env python3
"""
Regenerate [SEMANTICS ...] tags on MODULE-LEVEL #region headers only.
Generates MEANINGFUL, content-derived keywords for semantic graph search/discovery.
Uses word-boundary matching to prevent false positives from substrings.
"""
import subprocess, re, os, sys

WORKSPACE = "/home/busya/dev/ss-tools"

# Domain → tag mapping (path fragments)
PATH_DOMAINS = {
    'translate': 'translate', 'git': 'git', 'auth': 'auth',
    'dashboard': 'dashboard', 'migration': 'migration',
    'dataset': 'dataset', 'superset': 'superset', 'llm': 'llm',
    'storage': 'storage', 'backup': 'backup', 'health': 'health',
    'notification': 'notification', 'report': 'report',
    'assistant': 'assistant', 'rbac': 'rbac', 'role': 'rbac',
    'permission': 'rbac', 'clean_release': 'clean-release',
    'task': 'task', 'mapping': 'mapping', 'profile': 'profile',
    'scheduler': 'scheduler', 'plugin': 'plugin',
    'encrypt': 'encryption', 'resource': 'resource',
    'provider': 'provider', 'environ': 'environment', 'gitea': 'gitea',
    'connection': 'connection', 'preview': 'preview',
    'clarification': 'clarification', 'finding': 'finding',
    'execution': 'execution', 'session': 'session', 'filter': 'filter',
    'admin': 'admin', 'user': 'user', 'logs': 'logs',
    'policy': 'policy', 'compliance': 'compliance',
    'approval': 'approval', 'publication': 'publication',
    'artifact': 'artifact', 'manifest': 'manifest',
    'candidate': 'candidate', 'audit': 'audit', 'review': 'review',
    'datasets': 'dataset', 'dto': 'dto', 'enums': 'enum',
    'exceptions': 'exception', 'facade': 'facade',
    'clickhouse': 'clickhouse', 'sqlite': 'sqlite',
    'sidebar': 'sidebar', 'activity': 'activity', 'history': 'history',
    'orchestrator': 'orchestration', 'model': 'model',
}

# Operation → patterns (word-boundary matched against region text)
OP_MAP = {
    'sync': ['sync', 'synchronize', 'syncing'],
    'validate': ['validate', 'validation', 'validator'],
    'transform': ['transform', 'conver', 'parse', 'normalize'],
    'schedule': ['schedule', 'scheduler', 'cron', 'periodic'],
    'upload': ['upload'],
    'download': ['download'],
    'search': ['search', 'find', 'query'],
}

# Framework patterns (checked via word-boundary match)
PY_FRAMEWORKS = [
    ('fastapi', ['FastAPI', 'APIRouter']),
    ('sqlalchemy', ['SQLAlchemy', 'declarative_base']),
    ('pydantic', ['BaseModel', 'Field']),
    ('clickhouse', ['ClickHouse']),
]
FRT_FRAMEWORKS = [
    ('svelte', ['Svelte', '{#each', '{#if', 'on:click']),
    ('sveltekit', ['SvelteKit', '$page', '$app']),
]
COMMON_FRAMEWORKS = [
    ('tenacity', ['tenacity', 'retry']),
]

# Brief extra tags (word-boundary matched against @BRIEF text)
BRIEF_EXTRA = {
    'config': 'config', 'template': 'template', 'render': 'render',
    'key': 'encryption', 'jwt': 'jwt', 'oauth': 'oauth',
    'token': 'token', 'webhook': 'webhook',
    'notification': 'notification', 'alert': 'notification',
    'metric': 'metrics', 'logging': 'logging', 'tracing': 'tracing',
    'deploy': 'deploy', 'release': 'release',
    'docker': 'docker', 'batch': 'batch',
    'cli': 'cli', 'scaffold': 'scaffold', 'generator': 'generator',
    'diff': 'diff', 'patch': 'diff',
    'i18n': 'i18n', 'localization': 'i18n', 'locale': 'i18n',
    'navigation': 'navigation', 'sidebar': 'sidebar',
    'drawer': 'drawer', 'chat': 'chat',
    'sort': 'sort', 'pagination': 'pagination',
    'selection': 'selection', 'dry-run': 'dry-run',
    'form': 'form', 'table': 'table', 'modal': 'modal',
    'filter': 'filter', 'search': 'search',
    'history': 'history', 'activity': 'activity',
    'encryption': 'encryption', 'credential': 'credential',
    'archive': 'archive', 'restore': 'restore',
    'schedule': 'schedule', 'sync': 'sync',
}


def word_in_text(word, text):
    """Check if word appears as a whole word in text (case-insensitive, word-boundary)."""
    return bool(re.search(r'\b' + re.escape(word) + r'\b', text, re.IGNORECASE))


def any_word_in_text(words, text):
    """Check if any word from list appears as whole word in text."""
    return any(word_in_text(w, text) for w in words)


def is_frontend(fp):
    return os.path.splitext(fp)[1].lower() in ('.js', '.svelte', '.ts', '.css', '.html')


def is_python(fp):
    return fp.endswith('.py')


def read_region_text(content, line_num):
    lines = content.split('\n')
    result = []
    for i in range(line_num, min(line_num + 80, len(lines))):
        s = lines[i].strip()
        # Stop at region/def closers (use 'in' to handle '# #endregion' style)
        if '#endregion' in s or '[/DEF' in s:
            break
        result.append(lines[i])
    return '\n'.join(result)


def analyze(file_path, region_text):
    is_fe = is_frontend(file_path)
    pl = file_path.lower()
    bl = region_text.lower()
    kw = []

    # 1. Framework detection (whole-word or known identifier)
    if is_python(file_path):
        for fw, pats in PY_FRAMEWORKS:
            if any_word_in_text(pats, region_text):
                kw.append(fw)
                break
    if is_fe:
        for fw, pats in FRT_FRAMEWORKS:
            if any_word_in_text(pats, region_text):
                kw.append(fw)
                break
    for fw, pats in COMMON_FRAMEWORKS:
        if any_word_in_text(pats, region_text):
            kw.append(fw)
            break

    # 2. Domain from path
    for key, tag in PATH_DOMAINS.items():
        if key in pl and tag not in kw:
            kw.append(tag)

    # 3. Role from path
    if '/routes/' in pl or '/api/' in pl:
        kw.append('api')
    if '/services/' in pl:
        kw.append('service')
    if '/plugins/' in pl:
        kw.append('plugin')
    if '/models/' in pl or '/schemas/' in pl:
        kw.append('schema')
    if '/components/' in pl:
        kw.append('ui')
    if '/stores/' in pl:
        kw.append('store')
    if '/pages/' in pl or '+page' in pl:
        kw.append('page')
    if '/__tests__/' in pl or '/tests/' in pl or 'test_' in os.path.basename(file_path):
        kw.append('test')
    if '/repositories/' in pl:
        kw.append('repository')
    if '/orchestrator' in pl:
        kw.append('orchestration')
    if file_path.endswith('__init__.py') and 'package' not in kw:
        kw.append('package')

    # 4. Operations (word-boundary matched)
    for tag, pats in OP_MAP.items():
        if tag not in kw and any_word_in_text(pats, region_text):
            kw.append(tag)

    # 5. @BRIEF scanning
    bm = re.search(r'@BRIEF\s+(.*?)(?:\n|$)', region_text)
    if bm:
        brief = bm.group(1).strip()
        brief_lower = brief.lower()
        for key, tag in PATH_DOMAINS.items():
            if word_in_text(key, brief) and tag not in kw:
                kw.append(tag)
        for key, tag in BRIEF_EXTRA.items():
            if word_in_text(key, brief) and tag not in kw:
                kw.append(tag)

    # 6. Key classes as concepts
    for cls in re.findall(r'class\s+(\w+)', region_text)[:2]:
        clean = re.sub(r'([A-Z])', r' \1', cls).strip().lower()
        cw = [w for w in clean.split() if len(w) > 1]
        ch = '-'.join(cw)
        if ch and ch not in kw and len(ch) > 2:
            kw.append(ch)

    # 7. Deduplicate, limit to 6
    seen = set()
    result = []
    for k in kw:
        if k not in seen:
            seen.add(k)
            result.append(k)
    return result[:6]


def main():
    r = subprocess.run(['grep', '-rln', r'\[SEMANTICS',
                        os.path.join(WORKSPACE, 'backend/src'),
                        os.path.join(WORKSPACE, 'frontend/src')],
                       capture_output=True, text=True, cwd=WORKSPACE)
    files = sorted(f.strip() for f in r.stdout.split('\n') if f.strip())
    print(f"Found {len(files)} files with [SEMANTICS] tags")

    stats = {'updated': 0, 'skipped': 0, 'errors': 0, 'examples': []}

    for fp in files:
        if not os.path.exists(fp):
            stats['errors'] += 1
            continue
        rel = os.path.relpath(fp, WORKSPACE)

        with open(fp, 'r') as f:
            content = f.read()

        lines = content.split('\n')
        modified = False

        for i, line in enumerate(lines):
            s = line.strip()
            type_match = re.search(r'\[TYPE\s+(\w+)\]', s)
            def_match = re.match(r'^\s*(?:#|//|<!--)\s*\[DEF:(\w+):(\w+)\]', s)
            region_match = re.match(r'^\s*(?:#|//|<!--)\s*#region\s+(\S+)', s)

            if not region_match and not def_match:
                continue

            # Determine if this is a module-level header
            is_module = False
            if type_match and type_match.group(1) in (
                'Module', 'Page', 'Service', 'Store', 'Package',
                'Component', 'Agent', 'Skill'
            ):
                is_module = True
            if def_match:
                is_module = True
            if region_match and not type_match:
                prev_regions = [j for j in range(i) if '#region' in lines[j] or '[DEF:' in lines[j]]
                if not prev_regions:
                    is_module = True

            if not is_module:
                continue

            sem_match = re.search(r'\[SEMANTICS\s*([^\]]*)\]', s)
            if not sem_match:
                continue

            old_sem = sem_match.group(1).strip()
            region_text = read_region_text(content, i)
            keywords = analyze(fp, region_text)
            new_sem = ', '.join(keywords) if keywords else old_sem

            old_set = set(k.strip().lower() for k in old_sem.split(','))
            new_set = set(keywords)
            if old_set == new_set and len(old_set) == len(new_set):
                continue

            new_line = re.sub(r'\[SEMANTICS\s*([^\]]*)\]', f'[SEMANTICS {new_sem}]', line)
            lines[i] = new_line
            modified = True

            if len(stats['examples']) < 20:
                stats['examples'].append((rel, old_sem, new_sem))

        if modified:
            with open(fp, 'w') as f:
                f.write('\n'.join(lines))
            stats['updated'] += 1
            if stats['updated'] % 100 == 0:
                print(f"  ... {stats['updated']} files updated")
        else:
            stats['skipped'] += 1

    print(f"\n{'='*60}")
    print(f"COMPLETE: {stats['updated']} updated, {stats['skipped']} skipped, {stats['errors']} errors")
    print(f"{'='*60}")
    print("\n--- SAMPLE CHANGES (first 20) ---")
    for rel, old, new in stats['examples']:
        print(f"\n  FILE: {rel}")
        print(f"    OLD: [{old}]")
        print(f"    NEW: [{new}]")

    os.makedirs(os.path.join(WORKSPACE, 'semantics/reports'), exist_ok=True)
    rp = os.path.join(WORKSPACE, 'semantics/reports/semantics_update_report.txt')
    with open(rp, 'w') as f:
        f.write(f"SEMANTICS Tags Update Report\n{'='*60}\n")
        f.write(f"Updated: {stats['updated']}\nSkipped: {stats['skipped']}\nErrors: {stats['errors']}\n\n")
        f.write("SAMPLE CHANGES:\n")
        for rel, old, new in stats['examples']:
            f.write(f"\n  {rel}\n    OLD: [{old}]\n    NEW: [{new}]\n")
    print(f"\nReport: {rp}")


if __name__ == '__main__':
    main()
