import bpy
import bmesh
from mathutils import Vector
import math

# =========================================================
# ADAPTIVE DENSITY LOOP BRIDGE
# Two selected loops in Multi-Object Edit Mode
# Progressive vertex density increase
# Uses triangles when needed
# =========================================================

new_object_name = "AdaptiveBridge"
segments = 6   # cantidad de rings intermedios

# =========================================================
# HELPERS
# =========================================================

def get_edit_mesh_objects():
    return [o for o in bpy.context.objects_in_mode if o.type == 'MESH']


def selected_edges_and_bm(obj):
    bm = bmesh.from_edit_mesh(obj.data)
    edges = [e for e in bm.edges if e.select]
    return bm, edges


def ordered_loop(edges):
    adj = {}

    for e in edges:
        v1, v2 = e.verts
        adj.setdefault(v1, []).append(v2)
        adj.setdefault(v2, []).append(v1)

    start = list(adj.keys())[0]
    out = [start]

    prev = None
    cur = start

    while True:
        neigh = adj[cur]
        nxt = neigh[0] if neigh[0] != prev else neigh[1]

        if nxt == start:
            break

        out.append(nxt)
        prev = cur
        cur = nxt

        if len(out) > len(adj) + 5:
            break

    return out


def world_points(obj, verts):
    return [obj.matrix_world @ v.co for v in verts]


def perimeter(loop):
    total = 0
    for i in range(len(loop)):
        total += (loop[(i+1)%len(loop)] - loop[i]).length
    return total


def resample(loop, count):
    lengths = []
    total = 0

    for i in range(len(loop)):
        l = (loop[(i+1)%len(loop)] - loop[i]).length
        lengths.append(l)
        total += l

    pts = []

    for n in range(count):
        d = total * n / count
        acc = 0

        for i in range(len(loop)):
            if acc + lengths[i] >= d:
                t = (d - acc) / lengths[i]
                p = loop[i].lerp(loop[(i+1)%len(loop)], t)
                pts.append(p)
                break
            acc += lengths[i]

    return pts


def align(loopA, loopB):
    n = len(loopB)
    best = 0
    best_score = 1e18

    for shift in range(n):
        score = 0
        for i in range(min(len(loopA), len(loopB))):
            score += (loopA[i % len(loopA)] - loopB[(i+shift)%n]).length

        if score < best_score:
            best_score = score
            best = shift

    return [loopB[(i+best)%n] for i in range(n)]


def bridge_diff_counts(bm, ringA, ringB):
    """
    Connect rings with different counts using triangles
    """
    ia = 0
    ib = 0
    na = len(ringA)
    nb = len(ringB)

    while ia < na or ib < nb:
        a0 = ringA[ia % na]
        a1 = ringA[(ia + 1) % na]
        b0 = ringB[ib % nb]
        b1 = ringB[(ib + 1) % nb]

        ta = (ia + 1) / na
        tb = (ib + 1) / nb

        try:
            if ta < tb:
                bm.faces.new((a0, a1, b0))
                ia += 1
            elif tb < ta:
                bm.faces.new((a0, b1, b0))
                ib += 1
            else:
                bm.faces.new((a0, a1, b1))
                bm.faces.new((a0, b1, b0))
                ia += 1
                ib += 1
        except:
            ia += (ta <= tb)
            ib += (tb <= ta)


# =========================================================
# MAIN
# =========================================================

objs = get_edit_mesh_objects()

if len(objs) != 2:
    raise Exception("Selecciona 2 objetos en multi-edit mode.")

loops = []

for obj in objs:
    bm, edges = selected_edges_and_bm(obj)

    if not edges:
        raise Exception(f"No hay loop seleccionado en {obj.name}")

    verts = ordered_loop(edges)
    pts = world_points(obj, verts)
    loops.append(pts)

loopA = loops[0]
loopB = loops[1]

# más chico -> A
if len(loopA) > len(loopB):
    loopA, loopB = loopB, loopA

countA = len(loopA)
countB = len(loopB)

# alinear loop final
loopB = align(loopA, loopB)

# probar orientación
test1 = sum((loopA[i] - loopB[i]).length for i in range(min(len(loopA), len(loopB))))
rev = list(reversed(loopB))
test2 = sum((loopA[i] - rev[i]).length for i in range(min(len(loopA), len(loopB))))

if test2 < test1:
    loopB = rev

# =========================================================
# GENERATE INTERMEDIATE RINGS
# =========================================================

rings_pts = []

# centros base
centerA = sum(loopA, Vector()) / len(loopA)
centerB = sum(loopB, Vector()) / len(loopB)

for s in range(segments + 2):
    t = s / (segments + 1)

    count = round(countA + (countB - countA) * t)

    Ares = resample(loopA, count)
    Bres = resample(loopB, count)

    ring = []

    center = centerA.lerp(centerB, t)

    for i in range(count):
        offA = Ares[i] - centerA
        offB = Bres[i] - centerB

        offset = offA.lerp(offB, t)

        ring.append(center + offset)

    rings_pts.append(ring)

# =========================================================
# BUILD NEW MESH
# =========================================================

mesh = bpy.data.meshes.new(new_object_name)
obj_new = bpy.data.objects.new(new_object_name, mesh)
bpy.context.collection.objects.link(obj_new)

bm_new = bmesh.new()
rings = []

for ring in rings_pts:
    verts = [bm_new.verts.new(p) for p in ring]
    rings.append(verts)

bm_new.verts.ensure_lookup_table()

for i in range(len(rings)-1):
    bridge_diff_counts(bm_new, rings[i], rings[i+1])

bm_new.to_mesh(mesh)
bm_new.free()

for p in obj_new.data.polygons:
    p.use_smooth = True

print("Adaptive Density Bridge creado.")