"""Lossless canonical-record references to already published static JSON.

This module performs no network access. Callers provide the known documents or
an allowlisted loader. Hashes bind references to an exact dataset snapshot.
"""
from copy import deepcopy
from hashlib import sha256
import json

WIRE_FORMAT = 'chanceries-records-ref/1'
ALLOWED_DOCUMENTS = frozenset({
    '/people.json', '/diplomats.json', '/catalogue.json',
    '/api/v1/countries.json', '/api/v1/events.json', '/api/v1/sources.json.gz',
})


def compact(value):
    return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(',', ':'))


def digest(value):
    return sha256(compact(value).encode('utf-8')).hexdigest()


def _part(value):
    return str(value).replace('~', '~0').replace('/', '~1')


def _derived_identity(recipe):
    if not isinstance(recipe, list) or not recipe:
        raise ValueError('Malformed derived identity recipe')
    kind = recipe[0]
    if kind == 'source' and len(recipe) == 2 and isinstance(recipe[1], str):
        prefix, seed = 'src', recipe[1]
    elif kind == 'appointment' and len(recipe) == 2 and isinstance(recipe[1], str):
        prefix, seed = 'assert', recipe[1] + '|held_by'
    elif kind in {'paragraph', 'relationship'} and len(recipe) == 3 and isinstance(recipe[1], str) and isinstance(recipe[2], int) and recipe[2] >= 0:
        prefix, seed = 'assert', recipe[1] + '|' + kind + '|' + str(recipe[2])
    else:
        raise ValueError('Unregistered derived identity recipe')
    return prefix + '-' + sha256(seed.encode('utf-8')).hexdigest()[:24]


def wire_bundle(bundle, public_documents, minimum_bytes=16):
    """Replace exact repeated values with verified static JSON pointers.

    ``public_documents`` maps an allowlisted absolute path to the JSON object
    actually emitted at that path. A full bundle remains the authoring/compiler
    interface; this representation changes only redundant release bytes.
    """
    if not isinstance(public_documents, dict) or set(public_documents) - ALLOWED_DOCUMENTS:
        raise ValueError('Wire dependencies must be known public data documents')
    recipes, derived_ids = [], {}
    def add_recipe(identifier, recipe):
        if _derived_identity(recipe) == identifier and identifier not in derived_ids:
            derived_ids[identifier] = len(recipes)
            recipes.append(recipe)
    for source in bundle.get('sources', []):
        add_recipe(source['id'], ['source', source['url']])
    for claim in bundle.get('assertions', []):
        if not claim.get('provenance', {}).get('projection'):
            continue
        if claim.get('predicate') == 'narrative_paragraph' and isinstance(claim.get('value'), dict):
            add_recipe(claim['id'], ['paragraph', claim['subjectId'], claim['value'].get('paragraph')])
        elif claim.get('predicate') == 'held_by':
            add_recipe(claim['id'], ['appointment', claim['subjectId']])
        else:
            source_id = claim.get('provenance', {}).get('sourceId', '')
            if '/relationships/' in source_id and source_id.rsplit('/', 1)[-1].isdigit():
                add_recipe(claim['id'], ['relationship', claim['subjectId'], int(source_id.rsplit('/', 1)[-1])])
    candidates = {}
    object_candidates = {}
    def index(value, reference):
        text = compact(value)
        # Repeated pointer prefixes compress strongly even when a literal short
        # URL is smaller before compression. Measure the actual release bytes.
        if len(text.encode('utf-8')) >= minimum_bytes:
            h = sha256(text.encode('utf-8')).hexdigest()
            previous = candidates.get(h)
            if previous is None or len(reference) < len(previous):
                candidates[h] = reference
        if isinstance(value, dict):
            for field in ('id', 'url', 'title', 'name'):
                token = value.get(field)
                if isinstance(token, str) and len(token) >= 8:
                    options = object_candidates.setdefault((field, token), [])
                    if len(options) < 8:
                        options.append((reference, value))
            for key, child in value.items():
                index(child, reference + '/' + _part(key))
        elif isinstance(value, list):
            for n, child in enumerate(value):
                index(child, reference + '/' + str(n))
    for path, document in sorted(public_documents.items()):
        index(document, path + '#')
    dependencies = set()
    replacements = 0
    def encode(value):
        nonlocal replacements
        if isinstance(value, str) and value in derived_ids:
            return {'$id': derived_ids[value]}
        text = compact(value)
        if len(text.encode('utf-8')) >= minimum_bytes:
            reference = candidates.get(sha256(text.encode('utf-8')).hexdigest())
            if reference:
                dependencies.add(reference.split('#', 1)[0])
                replacements += 1
                return {'$ref': reference}
        if isinstance(value, dict):
            if set(value) == {'$ref'} or '$base' in value:
                raise ValueError('Literal $ref object conflicts with wire reference syntax')
            encoded = {key: encode(child) for key, child in value.items()}
            if value.get('recordType') and value.get('revision') == digest({k: v for k, v in value.items() if k != 'revision'})[:20]:
                encoded['revision'] = {'$revision': True}
            best = encoded
            best_size = len(compact(encoded))
            seen = set()
            for field in ('id', 'url', 'title', 'name'):
                token = value.get(field)
                if not isinstance(token, str):
                    continue
                for reference, base in object_candidates.get((field, token), []):
                    if reference in seen:
                        continue
                    seen.add(reference)
                    unchanged = {k for k in value if k in base and value[k] == base[k]}
                    if len(unchanged) < 3:
                        continue
                    changed = {k: encoded[k] for k in value if k not in unchanged}
                    removed = sorted(set(base) - set(value))
                    patch = {'$base': reference, '$set': changed, '$drop': removed}
                    size = len(compact(patch))
                    if size + 8 < best_size:
                        best, best_size = patch, size
            if '$base' in best:
                dependencies.add(best['$base'].split('#', 1)[0])
                replacements += 1
            return best
        if isinstance(value, list):
            return [encode(child) for child in value]
        return value
    graph = encode(bundle)
    encoded_recipes = [encode(recipe) for recipe in recipes]
    def count_references(value):
        if isinstance(value, dict):
            return int('$ref' in value or '$base' in value) + sum(count_references(x) for x in value.values())
        if isinstance(value, list):
            return sum(count_references(x) for x in value)
        return 0
    return {'schemaVersion': bundle['schemaVersion'], 'wireFormat': WIRE_FORMAT,
        'dependencies': {path: {'sha256': digest(public_documents[path])} for path in sorted(dependencies)},
        'derivedIds': encoded_recipes,
        'graph': graph, 'hydratedSha256': digest(bundle),
        'referenceCount': count_references(graph) + count_references(encoded_recipes)}


def hydrate_wire_bundle(wire, loader):
    """Resolve only declared local static references and verify exact parity.

    ``loader`` is a path-to-document mapping or a callable receiving one of the
    fixed paths above. It must read the same release; no arbitrary URLs are used.
    """
    if wire.get('wireFormat') != WIRE_FORMAT:
        raise ValueError('Unsupported canonical record wire format')
    dependencies = wire.get('dependencies')
    if not isinstance(dependencies, dict) or set(dependencies) - ALLOWED_DOCUMENTS:
        raise ValueError('Untrusted canonical record dependency')
    docs = {}
    for path, metadata in dependencies.items():
        value = loader[path] if isinstance(loader, dict) else loader(path)
        if digest(value) != metadata.get('sha256'):
            raise ValueError('Canonical dependency changed; retry the dataset snapshot: ' + path)
        docs[path] = value
    derived_ids = []
    def decode(value):
        if isinstance(value, dict) and set(value) == {'$id'}:
            index = value['$id']
            if not isinstance(index, int) or isinstance(index, bool) or not 0 <= index < len(derived_ids):
                raise ValueError('Unresolved derived canonical identity')
            return derived_ids[index]
        if isinstance(value, dict) and set(value) == {'$base', '$set', '$drop'}:
            base = decode({'$ref': value['$base']})
            if not isinstance(base, dict) or not isinstance(value['$set'], dict) or not isinstance(value['$drop'], list) or not all(isinstance(k, str) and k in base for k in value['$drop']):
                raise ValueError('Malformed canonical record object patch')
            for key in value['$drop']:
                del base[key]
            base.update({key: decode(child) for key, child in value['$set'].items()})
            if base.get('revision') == {'$revision': True}:
                base['revision'] = digest({k: v for k, v in base.items() if k != 'revision'})[:20]
            return base
        if isinstance(value, dict) and set(value) == {'$ref'}:
            reference = value['$ref']
            if not isinstance(reference, str) or reference.count('#') != 1:
                raise ValueError('Malformed canonical record reference')
            path, pointer = reference.split('#', 1)
            if path not in docs or (pointer and not pointer.startswith('/')):
                raise ValueError('Undeclared canonical record reference')
            found = docs[path]
            for part in pointer.lstrip('/').split('/') if pointer else []:
                if any(part[i] == '~' and (i + 1 == len(part) or part[i + 1] not in '01') for i in range(len(part))):
                    raise ValueError('Malformed JSON pointer escape')
                part = part.replace('~1', '/').replace('~0', '~')
                try:
                    if isinstance(found, list):
                        if not part.isdigit() or (len(part) > 1 and part.startswith('0')):
                            raise ValueError('Noncanonical JSON pointer array index')
                        found = found[int(part)]
                    elif isinstance(found, dict):
                        found = found[part]
                    else:
                        raise ValueError('JSON pointer traverses a scalar')
                except (KeyError, IndexError) as exc:
                    raise ValueError('Unresolved canonical record pointer') from exc
            # Referenced data are values, never instructions for another fetch.
            return deepcopy(found)
        if isinstance(value, dict):
            result = {key: decode(child) for key, child in value.items()}
            if result.get('revision') == {'$revision': True}:
                result['revision'] = digest({k: v for k, v in result.items() if k != 'revision'})[:20]
            return result
        if isinstance(value, list):
            return [decode(child) for child in value]
        return value
    for recipe in wire.get('derivedIds', []):
        derived_ids.append(_derived_identity(decode(recipe)))
    bundle = decode(wire.get('graph'))
    if not isinstance(bundle, dict) or bundle.get('schemaVersion') != wire.get('schemaVersion') or digest(bundle) != wire.get('hydratedSha256'):
        raise ValueError('Canonical record hydration checksum mismatch')
    return bundle
