#!/usr/bin/env python3
"""Check catalog metadata, local links, and duplicate names without mutating files."""
import argparse
import json
from pathlib import Path
import re
import sys


def metadata(text):
    """Read the catalog's scalar and block-string YAML frontmatter subset."""
    match = re.match(r'\A---\r?\n(.*?)\r?\n---(?:\r?\n|$)', text, re.S)
    if not match:
        raise ValueError('missing frontmatter')
    result = {}
    field = None
    for line in match[1].splitlines():
        if not line.strip() or line.lstrip().startswith('#'):
            continue
        if line[0].isspace():
            if field is None:
                raise ValueError('unexpected indentation in frontmatter')
            result[field] += ' ' + line.strip()
            continue
        item = re.fullmatch(r'([a-z][a-z0-9_-]*):(?:\s+(.*))?', line)
        if not item:
            raise ValueError('invalid frontmatter field')
        key, value = item.groups()
        if key in result:
            raise ValueError(f'duplicate field: {key}')
        value = (value or '').strip()
        field = key if value in ('>', '|', '>-', '|-') else None
        if field:
            result[key] = ''
        else:
            if value.startswith('"'):
                try:
                    result[key] = json.loads(value)
                except json.JSONDecodeError as exc:
                    raise ValueError(f'invalid quoted field: {key}') from exc
            elif value.startswith("'"):
                if len(value) < 2 or not value.endswith("'"):
                    raise ValueError(f'invalid quoted field: {key}')
                result[key] = value[1:-1].replace("''", "'")
            else:
                if re.search(r':\s', value):
                    raise ValueError(f'invalid plain scalar: {key}; use a block string')
                result[key] = value.split(' #', 1)[0]
    return {k: v.strip() for k, v in result.items()}


def local_links(path):
    fenced = False
    for number, line in enumerate(path.read_text().splitlines(), 1):
        if line.lstrip().startswith(('```', '~~~')):
            fenced = not fenced
            continue
        if fenced:
            continue
        for raw in re.findall(r'\]\(([^)]+)\)', line):
            target = raw.strip().split('#', 1)[0]
            if not target or re.match(r'[a-zA-Z][a-zA-Z0-9+.-]*:', target):
                continue
            target = target.strip('<>')
            resolved = path.parent / target
            if not resolved.exists():
                yield f'{path}:{number}: missing link target {target}'


def main(argv=None):
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--global-dir', type=Path, default=Path(__file__).resolve().parents[1] / 'skills')
    parser.add_argument('--jev-dir', type=Path, default=Path.home() / 'Projects/work/JEV/.agents/skills')
    args = parser.parse_args(argv)
    errors = []
    names = {}
    count = 0
    if not args.global_dir.is_dir():
        parser.error(f'global catalog unavailable: {args.global_dir}')
    roots = [args.global_dir]
    if args.jev_dir.is_dir():
        roots.append(args.jev_dir)
    else:
        print(f'WARN: JEV catalog unavailable: {args.jev_dir}', file=sys.stderr)
    for root in roots:
        for directory in sorted(root.iterdir()):
            if not directory.is_dir():
                continue
            path = directory / 'SKILL.md'
            if not path.is_file():
                errors.append(f'{directory}: missing SKILL.md')
                continue
            count += 1
            try:
                fields = metadata(path.read_text())
                name = fields.get('name', '')
                if name != directory.name or not re.fullmatch(r'[a-z0-9]+(?:-[a-z0-9]+)*', name):
                    errors.append(f'{path}: invalid or mismatched name')
                if not fields.get('description'):
                    errors.append(f'{path}: empty description')
                if fields.get('scope') not in ('global', 'project', 'alias'):
                    errors.append(f'{path}: scope must be global, project, or alias')
                if name in names:
                    errors.append(f'{path}: name collides with {names[name]}')
                names[name] = path
            except ValueError as exc:
                errors.append(f'{path}: {exc}')
        for path in sorted(root.rglob('*.md')):
            errors.extend(local_links(path))
    # Global protocols are part of this catalog; JEV protocols belong to its source.
    for folder in [args.global_dir.parent / 'protocols', args.jev_dir.parent / 'protocols']:
        if folder.is_dir():
            for path in sorted(folder.glob('*.md')):
                errors.extend(local_links(path))
    readme = args.global_dir.parent / 'README.md'
    if readme.is_file():
        errors.extend(local_links(readme))
    for error in errors:
        print(error, file=sys.stderr)
    print(f'catalog: {count} skills, {len(errors)} errors')
    return bool(errors)


if __name__ == '__main__':
    sys.exit(main())
