#!/usr/bin/env python3
"""
Build directory tree from ils output using fls parent pointers.
For each directory inode:
  1. Run fls to get its contents and parent (..)
  2. Record parent->child relationships  
  3. Walk parent chain to resolve full path
  4. Place unreachable dirs in /orphans/
"""
import subprocess, sys, os, collections

DEVICE   = '/dev/nbd0'
OUTDIR   = '/mnt/recovered'
MIN_INODE = 106497  # first intact group

def fls(inode):
    try:
        r = subprocess.run(
            ['fls', DEVICE, str(inode)],
            capture_output=True, text=True, timeout=30
        )
        entries = []
        for line in r.stdout.splitlines():
            try:
                parts = line.split(None, 2)
                if len(parts) < 3: continue
                type_str  = parts[0]
                ino_str   = parts[1].rstrip(':').lstrip('*')
                name      = parts[2].strip()
                ino       = int(ino_str)
                etype     = type_str[0]
                entries.append((etype, ino, name))
            except: continue
        return entries
    except:
        return []

# Load directory inodes from ils output
print('Loading directory inodes...')
dir_inodes = []
with open('/tmp/dir_inodes.txt') as f:
    for line in f:
        parts = line.strip().split('|')
        if len(parts) < 2: continue
        try:
            ino = int(parts[0])
            if ino >= MIN_INODE:
                dir_inodes.append(ino)
        except: continue

print(f'Found {len(dir_inodes)} directory inodes to process')

# For each directory, get its parent and children
# parent_of[inode] = parent_inode
# children_of[inode] = [(child_inode, name, type)]
parent_of   = {}
children_of = collections.defaultdict(list)
names       = {}  # inode -> name (as seen from parent)

print('Running fls on each directory...')
for idx, ino in enumerate(dir_inodes):
    entries = fls(ino)
    for etype, eino, ename in entries:
        if ename == '..':
            parent_of[ino] = eino
        elif ename == '.':
            continue
        else:
            children_of[ino].append((eino, ename, etype))
            names[eino] = ename

    if idx % 1000 == 0:
        print(f'  {idx}/{len(dir_inodes)} processed...', flush=True)

print(f'Built tree: {len(parent_of)} dirs with known parents')

# Resolve full paths by walking parent chain
resolved = {}  # inode -> full path

def resolve(ino, depth=0):
    if ino in resolved:
        return resolved[ino]
    if depth > 50:
        return None

    parent = parent_of.get(ino)
    if parent is None:
        path = f'orphans/{ino}'
        resolved[ino] = path
        return path

    # Check if parent is in intact groups
    if parent < MIN_INODE:
        # Parent is in zeroed groups — this is a root-level orphan
        # Use the name if we know it
        name = names.get(ino, str(ino))
        path = f'orphans/{name}_{ino}'
        resolved[ino] = path
        return path

    parent_path = resolve(parent, depth + 1)
    if parent_path is None:
        path = f'orphans/{ino}'
    else:
        name = names.get(ino, str(ino))
        path = os.path.join(parent_path, name)

    resolved[ino] = path
    return path

print('Resolving paths...')
for ino in dir_inodes:
    resolve(ino)

# Print summary
orphans  = sum(1 for p in resolved.values() if p.startswith('orphans'))
resolved_count = len(resolved) - orphans
print(f'Resolved paths: {resolved_count}')
print(f'Orphaned dirs:  {orphans}')
print()

# Show interesting paths
print('Sample resolved paths:')
for ino, path in sorted(resolved.items(), key=lambda x: x[1]):
    if any(x in path for x in ['var','pterodactyl','docker','mysql',
                                'www','log','lib']):
        print(f'  inode {ino:10d}: {path}')

# Save full tree
with open('/tmp/dir_tree.txt','w') as f:
    for ino, path in sorted(resolved.items(), key=lambda x: x[1]):
        f.write(f'{ino}\t{path}\n')
print(f'Saved {len(resolved)} paths to /tmp/dir_tree.txt')
