#!/usr/bin/env python3
"""Generate read-only maps from explicitly scoped project facts."""
import argparse
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1]/'src'))
from arb_project import load_project, attach_evidence, safe_path, ProjectError
from arb_views import _frontmatter
from arb_evidence import asset_version_change


def read_json(path):
    def pairs(items):
        result = {}
        for key, value in items:
            if key in result: raise ProjectError('duplicate JSON key: ' + key)
            result[key] = value
        return result
    return json.loads(path.read_text(encoding='utf-8'), object_pairs_hook=pairs)


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('project_root', type=Path)
    parser.add_argument('--vault-root', type=Path, required=True)
    parser.add_argument('--asset', action='append', default=[])
    parser.add_argument('--binding', action='append', default=[], help='project-relative JSON source pointers; never task state')
    parser.add_argument('--previous', help='project-relative previous arb-project JSON snapshot; comparison only')
    parser.add_argument('--format', choices=('json','markdown'), default='json')
    parser.add_argument('--output-path',type=Path,help='Markdown destination context only; never written')
    parser.add_argument('--json-link',type=Path,help='existing machine output to link from Markdown')
    args = parser.parse_args()
    try:
        output = load_project(args.project_root, args.vault_root, args.asset)
        root = args.project_root.resolve()
        allowed = [root]
        declared = _frontmatter(safe_path(root, 'PROJECT.md')).get('sourceRoot')
        if declared:
            source = Path(declared)
            if not source.is_absolute() or source.is_symlink() or source.resolve() == Path('/'):
                raise ProjectError('invalid registered source root')
            allowed.append(source.resolve())
        requests = []
        for relative in args.binding:
            descriptor = read_json(safe_path(root, relative))
            if not isinstance(descriptor, dict) or set(descriptor) != {'report', 'current_sources'}:
                raise ProjectError('binding descriptor requires report/current_sources only')
            sources = descriptor['current_sources']
            if not isinstance(sources, dict) or 'allowed_roots' in sources:
                raise ProjectError('binding cannot override project source boundary')
            requests.append({'report':descriptor['report'], 'current_sources':{**sources,'allowed_roots':allowed}})
        if requests: output = attach_evidence(output, requests)
        if args.previous:
            previous = read_json(safe_path(root, args.previous))
            old = previous['graph']['nodes']
            if not isinstance(old, list) or any(not isinstance(n,dict) or not isinstance(n.get('id'),str) for n in old):
                raise ProjectError('invalid previous graph nodes')
            indexed = {n['id']: n for n in old}
            if len(indexed) != len(old): raise ProjectError('duplicate previous identity')
            current = {n['id']:n for n in output['graph']['nodes']}
            output['versionChecks'] = {key: asset_version_change(indexed[key],current[key])
                if key in indexed and key in current else ('ADDED' if key in current else 'REMOVED')
                for key in sorted(set(indexed)|set(current))}
        if args.format=='markdown':
            if args.output_path is None: raise ProjectError('Markdown requires --output-path')
            from arb_markdown import render_project
            print(render_project(output,args.vault_root,args.output_path,args.json_link),end='')
        else: print(json.dumps(output, ensure_ascii=False, sort_keys=True, indent=2, allow_nan=False))
        return 1 if output['conflicts'] or any(v in ('FAIL','UNKNOWN','REMOVED') for v in output.get('versionChecks',{}).values()) else 0
    except (OSError, ValueError, TypeError, KeyError, AttributeError) as error:
        print(f'arb-project: ERROR: {error}; sources unchanged; correct input and regenerate', file=sys.stderr)
        return 2


if __name__ == '__main__': raise SystemExit(main())
