import re

def load(p):
    raw = open(p, encoding='utf-8', newline='').read()
    return raw.replace('\r\n', '\n'), '\r\n' in raw

def save(p, s, crlf):
    open(p, 'w', encoding='utf-8', newline='').write(s.replace('\n', '\r\n') if crlf else s)

def once(s, old, new):
    assert s.count(old) == 1, (old[:80], s.count(old))
    return s.replace(old, new)

def add_after_field(s, opener, field, line, expect):
    out, pos, n = [], 0, 0
    for m in re.finditer(re.escape(opener), s):
        if m.start() < pos or s[max(0, m.start() - 7):m.start()] == 'struct ':
            continue
        fm = re.compile(r'\n([ \t]*)' + re.escape(field) + r'[^\n]*').search(s, m.end())
        close = s.find('\n', s.find('}', m.end()))
        assert fm and fm.start() < close, (opener, s[m.end():m.end() + 200])
        out.append(s[pos:fm.end()])
        out.append('\n' + fm.group(1) + line)
        pos = fm.end()
        n += 1
    out.append(s[pos:])
    assert n == expect, (opener, n, expect)
    return ''.join(out)

p = 'crates/spt-net/src/net/update.rs'
s, c = load(p)
s = once(s, '''    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub signing_key: Option<String>,
}
''', '''    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub signing_key: Option<String>,
    /// The manifest's `hostable_types`, verbatim (releases#264 / #269): what
    /// lets a peer group a remote adapter as Gateway, Shell or Harness before
    /// installing it. Additive: an older peer's row omits it and decodes to an
    /// empty list (grouped by `kind`), and an older decoder ignores the key.
    /// Empty on an [`UpdRecord::AdapterOffer`], which is built from the
    /// retained sidecar; the roster row on the registry feed carries it.
    // [impl->REQ-ADAPTER-LIST-SURFACE]
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub hostable_types: Vec<String>,
}
''')
s = add_after_field(s, 'AdapterRosterRow {', 'signing_key', 'hostable_types: Vec::new(),', 2)
save(p, s, c)

for p, n in [('crates/spt/src/cli.rs', 1), ('crates/spt/src/wansend.rs', 1), ('crates/spt/tests/adapter_peer_e2e.rs', 1),
             ('crates/spt/tests/source_ladder_e2e.rs', 1), ('crates/spt-daemon/src/propagate.rs', 1),
             ('crates/spt-daemon/src/relcache.rs', 1), ('crates/spt-net/src/net/registry.rs', 1),
             ('crates/spt-net/src/net/replicate.rs', 1)]:
    s, c = load(p)
    s = add_after_field(s, 'AdapterRosterRow {', 'signing_key', 'hostable_types: Vec::new(),', n)
    save(p, s, c)

p = 'crates/spt-daemon/src/relcache.rs'
s, c = load(p)
s = once(s, '''                let kept = self.retained_adapter(&r.name)?;
                (kept.version == m.adapter.version).then(|| kept.row(&r.name))''',
'''                let kept = self.retained_adapter(&r.name)?;
                // The sidecar holds no hostable types; the manifest the node
                // runs does, and the roster is where a peer reads them.
                // [impl->REQ-ADAPTER-LIST-SURFACE]
                (kept.version == m.adapter.version).then(|| AdapterRosterRow {
                    hostable_types: m.adapter.hostable_types.clone(),
                    ..kept.row(&r.name)
                })''')
save(p, s, c)
print('ok')
