"""IMM1294-only phone visibility and scoped spouse dataset targets."""
import re
from xml.parsers import expat


def contact_phone_state(xml_bytes: bytes, fields: dict[str, str]) -> bytes:
    states = {}
    for group in ('Phone', 'AltPhone'):
        prefix = f'PhoneNumbers.{group}.'
        canada = fields.get(prefix + 'CanadaUS')
        other = fields.get(prefix + 'Other')
        if (canada, other) in [('1', '0'), ('0', '1')]:
            # EN and FR blanks use different page/container hierarchies.
            for root in ('form1.Page3.', 'form1.Page2.contact.'):
                states[root + prefix + 'NANumber'] = 'visible' if canada == '1' else 'invisible'
                states[root + prefix + 'IntlNumber'] = 'invisible' if canada == '1' else 'visible'
    if not states:
        return xml_bytes

    # XFA packets contain processing instructions and namespace-sensitive content.
    # Locate attributes with a parser, but preserve every other original byte.
    parser = expat.ParserCreate(namespace_separator='}')
    paths = []
    edits = []

    def start(tag, attrs):
        path = paths[-1] if paths else ''
        if attrs.get('name'):
            path = (path + '.' + attrs['name']).lstrip('.')
        paths.append(path)
        if path not in states or tag.rsplit('}', 1)[-1] != 'subform':
            return
        offset = parser.CurrentByteIndex
        end = offset
        quote = None
        while end < len(xml_bytes):
            char = xml_bytes[end]
            if quote is not None:
                if char == quote:
                    quote = None
            elif char in (34, 39):
                quote = char
            elif char == 62:
                break
            end += 1
        opening = xml_bytes[offset:end + 1]
        presence = states[path].encode('ascii')
        pattern = br'(\bpresence\s*=\s*[\x22\x27])[^\x22\x27]*([\x22\x27])'
        if re.search(pattern, opening):
            opening = re.sub(pattern, lambda m: m[1] + presence + m[2], opening, count=1)
        else:
            insert = len(opening) - (2 if opening.endswith(b'/>') else 1)
            opening = opening[:insert] + b' presence="' + presence + b'"' + opening[insert:]
        edits.append((offset, end + 1, opening))

    parser.StartElementHandler = start
    parser.EndElementHandler = lambda tag: paths.pop()
    parser.Parse(xml_bytes, True)
    for start, end, replacement in reversed(edits):
        xml_bytes = xml_bytes[:start] + replacement + xml_bytes[end:]
    return xml_bytes


def spouse_values(fields: dict[str, str]) -> dict[str, str]:
    """Scope the current spouse's name to its Page 1 scalar widget.

    Page 2 repeats GivenName as a container for PMGivenName. An unqualified
    parent write clears that container before the previous name can be written.
    Keep mapped payload keys stable; qualify only IMM1294's dataset write.
    """
    source = 'MaritalStatus.SectionA.GivenName'
    target = 'form1.Page1.MaritalStatus.SectionA.GivenName'
    if source in fields and target in fields and fields[source] != fields[target]:
        raise ValueError('Conflicting IMM1294 current-spouse given-name targets')
    return {target if key == source else key: value for key, value in fields.items()}
