"""Features v3: market = any contract; start S = first buy; windows from S. Contract map from runtrack addr_kind + getCode of frequent unknowns."""
import json, gzip, collections, time, re, os, bisect
from feat import AK, RT, SUP, KL, BPS, PM, Z, DEAD, tier_of
UK = json.load(open('/tmp/run/unk_kind.json')) if os.path.exists('/tmp/run/unk_kind.json') else {}
def is_contract(a):
    if a in (PM,): return True
    k = AK.get(a)
    if k: return k != 'user'
    u = UK.get(a)
    return u == 'contract'
def is_user(a): return a not in (Z, DEAD) and not is_contract(a)
A = json.load(open('/tmp/run/anchors.json')); AB = [a[0] for a in A]
def b2t(b):
    i = min(max(bisect.bisect_left(AB, b), 1), len(A) - 1); (b0, t0), (b1, t1) = A[i - 1], A[i]
    return t0 + (b - b0) * (t1 - t0) / (b1 - b0)
def nets(tr):
    by = collections.defaultdict(list)
    for x in tr: by[x[5]].append(x)
    out = []
    for h, xs in by.items():
        net = collections.defaultdict(int); mint = False
        for (_, _, fr, to, val, _) in xs:
            v = int(val, 16) if val and val not in ('0x', '0x0') else 0
            if fr == Z: mint = True; net[to] += v; continue
            net[fr] -= v; net[to] += v
        out.append((xs[0][0], xs[0][1], h, net, mint))
    out.sort(key=lambda z: (z[0], z[1])); return out
def kind_of(net, mint):
    up = [a for a, v in net.items() if v > 0 and is_user(a)]; un = [a for a, v in net.items() if v < 0 and is_user(a)]
    cn = any(v < 0 and is_contract(a) for a, v in net.items()); cp = any(v > 0 and is_contract(a) for a, v in net.items())
    if mint: return 'mint'
    if up and cn and not un: return 'buy'
    if un and cp and not up: return 'sell'
    if up and un and not cn and not cp: return 'w2w'
    if up and cn: return 'buy'
    if un and cp: return 'sell'
    return 'other'
NAMEK = [('cat', r'\bcat|kitty|kitten|meow|mow\b|neko'), ('dog', r'\bdog|inu\b|doge|shib|pup'), ('frog', r'frog|pepe|frong'),
         ('robinhood', r'robin|hood|green ?hood|feather|sherwood'), ('ai', r'\bai\b|agent|gpt|intelligen|neural|brain|bot\b'),
         ('platform', r'\bfun\b|\.fun|pad\b|launch|swap|dex|fi\b|finance|protocol|exchange|market|index|pool|hook|wallet|vault|stake|yield|pair|broker|route'),
         ('stock', r'tesla|spacex|nvidia|apple|amazon|stonk|stock|ipo|wall ?st'), ('person', r'ansem|elon|trump|musk|vitalik|cz\b|tate|speed')]
def name_kinds(n):
    n = (n or '').lower(); return [k for k, rx in NAMEK if re.search(rx, n)]
def feats3(o):
    tok = o['tok']; T = nets(o.get('tr') or []); L = o['launch_block']
    f = dict(tok=tok, kind=o['kind'], L=L)
    pools = o.get('pools') or []
    if pools:
        p0 = pools[0]
        if 'data' in p0:
            d = p0['data'][2:]; w = [d[i:i + 64] for i in range(0, len(d), 64)]
            f['hook'] = '0x' + w[2][-40:] if len(w) > 2 else None
        else: f['hook'] = p0.get('hook')
        f['quote'] = p0['c1'] if p0['c0'] == tok else p0['c0']
    f['factory'] = o.get('ptx_to'); f['creator'] = (o.get('ptx_from') or '').lower(); f['n_pools'] = len(pools)
    mints = o.get('mints') or []
    minted = sum(int(m[2], 16) if isinstance(m[2], str) and m[2].startswith('0x') else int(m[2]) for m in mints) or 1
    supply = None
    if o.get('supply') and str(o['supply']).isdigit(): supply = int(o['supply'])
    elif tok in SUP: supply = int(SUP[tok] * 10 ** int(o.get('decimals') or 18))
    supply = supply if supply and supply > 0 else minted
    f['supply_src_ok'] = int(supply != minted or bool(o.get('supply')))
    f['mint_to_user_share'] = min(1, sum((int(m[2], 16) if str(m[2]).startswith('0x') else int(m[2])) for m in mints if is_user(m[1])) / supply)
    S = next((b for b, li, h, net, m in T if kind_of(net, m) == 'buy'), None)
    f['S'] = S; f['first_buy_delay_s'] = (S - L) / BPS if S else None
    f['ts'] = o.get('launch_ts') or (b2t(L) if L else None)
    f['start_ts'] = b2t(S) if S else None
    f['cover_s'] = ((L + 18000) - S) / BPS if S else 0
    if not S: return f
    bal = collections.defaultdict(int)
    for m in mints: bal[m[1]] += int(m[2], 16) if str(m[2]).startswith('0x') else int(m[2])
    buys = []; sells = []; w2w = 0; w2w_wallets = set(); fs = None
    for b, li, h, net, m in T:
        sec = (b - S) / BPS
        for a, v in net.items():
            if not m: bal[a] += v
        k = kind_of(net, m)
        if sec < 0: continue
        if k == 'buy':
            for a, v in net.items():
                if v > 0 and is_user(a): buys.append((sec, a, v, b))
        elif k == 'sell':
            for a, v in net.items():
                if v < 0 and is_user(a): sells.append((sec, a, -v)); fs = sec if fs is None else fs
        elif k == 'w2w':
            w2w += 1; w2w_wallets.update(a for a, v in net.items() if v != 0)
    tot = supply
    for w in (10, 60, 300, 1800):
        f['buyers_%ds' % w] = len({a for t, a, v, b in buys if t <= w})
        f['share_bought_%ds' % w] = sum(v for t, a, v, b in buys if t <= w) / tot
    f['sells_30m'] = sum(1 for t, a, v in sells if t <= 1800); f['sellers_30m'] = len({a for t, a, v in sells if t <= 1800})
    f['sell_share_30m'] = sum(v for t, a, v in sells if t <= 1800) / tot
    f['first_sell_s'] = fs; f['w2w_30m'] = w2w; f['w2w_wallets'] = len(w2w_wallets)
    b0 = [x for x in buys if x[3] <= S + 2]
    f['blk0_buyers'] = len({a for t, a, v, b in b0}); f['blk0_share'] = sum(v for t, a, v, b in b0) / tot
    f['s10_share'] = f['share_bought_10s']
    first20 = [v for t, a, v, b in buys[:20]]
    if len(first20) >= 5:
        mu = sum(first20) / len(first20); f['cv_first20'] = (sum((x - mu) ** 2 for x in first20) / len(first20)) ** .5 / mu if mu else None
    hb = sorted(((v, a) for a, v in bal.items() if is_user(a) and v > 0), reverse=True)
    f['top1_end'] = hb[0][0] / tot if hb else 0; f['top10_end'] = sum(v for v, a in hb[:10]) / tot; f['holders_end'] = len(hb)
    early = {a for t, a, v, b in buys if t <= 60}
    f['early_hold_share_end'] = sum(bal.get(a, 0) for a in early if bal.get(a, 0) > 0) / tot
    f['creator_bought'] = int(any(a == f['creator'] for t, a, v, b in buys))
    f['creator_share_end'] = max(0, bal.get(f['creator'], 0)) / tot
    f['early5m'] = sorted({a for t, a, v, b in buys if t <= 300})
    f['early60'] = sorted(early)
    return f
