"""Named current dialogue fields; no client functions or inputs are invoked."""
from mini_masukja.native_type_reference import type_definition_index
from functools import lru_cache
import json
import struct
from .paths import root


@lru_cache(maxsize=1)
def direct_source():
    return json.loads((root()/'data/direct-dialogue-source.json').read_text(encoding='utf8'))


def action_branches(session, pointer, owner):
    """Read branch presence using the actual closed Optional layout.

    Report.OnDecline is an inline Optional<IDialogueActionData>. Its stale
    payload is not a live reference when HasValue is false.
    """
    r = session.reader
    schema = session.source_shape(owner)
    klass = r.u64(pointer)
    session.fields(klass, schema)
    result = []
    for name in ('<OnAccept>k__BackingField', '<OnDecline>k__BackingField'):
        field = next(f for f in schema['fields'] if f['name'] == name)
        ty = field['type']
        if ty.get('type_code') == 18:
            present = bool(struct.unpack('<Q', session.watch(pointer+field['offset'], 8))[0])
        else:
            args = ty.get('arguments', [])
            if (ty.get('name') != 'Optional`1' or ty.get('namespace') != 'MM.QDBL.Definitions.ValueType'
                    or len(args) != 1 or args[0].get('definition_index') != 37497
                    or args[0].get('type_code') != 18):
                raise ValueError('current dialogue branch declared type changed')
            table = r.u64(klass+0x80)
            tp = struct.unpack('<Q', session.watch(table+field['index']*32+8, 8))[0]
            if ((r.u32(tp+8)>>16)&255) != 21:
                raise ValueError('current dialogue Optional field type changed')
            generic = struct.unpack('<Q', session.watch(tp, 8))[0]
            closed = struct.unpack('<Q', session.watch(generic+24, 8))[0] if generic else 0
            if not closed or r.class_name(closed) != ('Optional`1', 'MM.QDBL.Definitions.ValueType'):
                raise ValueError('current dialogue Optional closed class unavailable')
            inst = r.u64(generic+8)
            if r.u64(inst) != 1:
                raise ValueError('current dialogue Optional generic arity changed')
            arg = r.u64(r.u64(inst+8))
            if ((r.u32(arg+8)>>16)&255) != 18 or type_definition_index(session, arg) != 37497:
                raise ValueError('current dialogue Optional argument domain changed')
            layout = r.layout(closed, ['value', '<HasValue>k__BackingField'])
            if r.class_name(int(layout['parent_class'], 16)) != ('ValueType', 'System'):
                raise ValueError('current dialogue Optional is not a value type')
            fields = {f['name']: f for f in layout['fields']}
            vf, hf = fields['value'], fields['<HasValue>k__BackingField']
            if (vf['type_code'], hf['type_code']) != (18, 2):
                raise ValueError('current dialogue Optional named fields changed')
            width = layout['instance_size']-16
            span = min([f['offset'] for f in schema['fields'] if f['offset'] > field['offset']]
                       + [schema['type_definition_size_raw'][0]])-field['offset']
            vo, ho = vf['offset']-16, hf['offset']-16
            if not (0 < width <= span and 0 <= vo <= width-8 and 0 <= ho < width
                    and not vo <= ho < vo+8):
                raise ValueError('current dialogue Optional offsets outside source span')
            raw = session.watch(pointer+field['offset'], width)
            if raw[ho] not in (0, 1):
                raise ValueError('current dialogue Optional presence invalid')
            present = bool(raw[ho])
            if present and not struct.unpack_from('<Q', raw, vo)[0]:
                raise ValueError('current dialogue Optional present branch is null')
        if present:
            result.append(name)
    return result
