import bpy
import bmesh
import math
from mathutils import Vector, Matrix

# Clear existing objects
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

def create_material(name, base_color, roughness=0.5, metallic=0.0):
    mat = bpy.data.materials.new(name=name)
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    bsdf = nodes.get("Principled BSDF")
    if bsdf:
        try:
            bsdf.inputs["Base Color"].default_value = base_color
        except Exception:
            pass
        try:
            bsdf.inputs["Roughness"].default_value = roughness
        except Exception:
            pass
        try:
            bsdf.inputs["Metallic"].default_value = metallic
        except Exception:
            pass
    return mat

body_brown = create_material("Body_Brown", (0.25, 0.18, 0.12, 1.0), 0.85)
breast_white = create_material("Breast_White", (0.95, 0.93, 0.88, 1.0), 0.7)
head_yellow = create_material("Head_Yellow", (0.95, 0.85, 0.55, 1.0), 0.6)
neck_brown = create_material("Neck_Brown", (0.35, 0.22, 0.12, 1.0), 0.75)
bill_gray = create_material("Bill_Gray", (0.5, 0.45, 0.4, 1.0), 0.35)
pouch_olive = create_material("Pouch_Olive", (0.35, 0.38, 0.28, 1.0), 0.8)
eye_yellow = create_material("Eye_Yellow", (0.95, 0.9, 0.5, 1.0), 0.2)
pupil_dark = create_material("Pupil_Dark", (0.02, 0.02, 0.02, 1.0), 0.1)
leg_dark = create_material("Leg_Dark", (0.15, 0.12, 0.1, 1.0), 0.6)
wing_dark = create_material("Wing_Dark", (0.08, 0.06, 0.04, 1.0), 0.85)
wing_silver = create_material("Wing_Silver", (0.6, 0.58, 0.55, 1.0), 0.75)
claw_dark = create_material("Claw_Dark", (0.1, 0.08, 0.06, 1.0), 0.4)

all_parts = []

def add_to_collection(obj, mat=None):
    if mat and obj.data:
        if len(obj.data.materials) == 0:
            obj.data.materials.append(mat)
        else:
            obj.data.materials[0] = mat
    all_parts.append(obj.name)

def create_ellipsoid(name, location, scale, segments=32, rings=16):
    bpy.ops.mesh.primitive_uv_sphere_add(segments=segments, ring_count=rings, radius=1.0, location=location)
    obj = bpy.context.active_object
    if obj:
        obj.name = name
        obj.scale = scale
        bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    return bpy.data.objects.get(name)

def create_cylinder_from_cone(name, location, radius1, radius2, depth, segments=16, rotation=(0, 0, 0)):
    bm = bmesh.new()
    bmesh.ops.create_cone(
        bm,
        cap_ends=True,
        cap_tris=False,
        segments=segments,
        radius1=radius1,
        radius2=radius2,
        depth=depth
    )
    mesh = bpy.data.meshes.new(name)
    bm.to_mesh(mesh)
    bm.free()
    obj = bpy.data.objects.new(name, mesh)
    bpy.context.collection.objects.link(obj)
    obj.location = location
    obj.rotation_euler = rotation
    return obj

# === BODY ===
body = create_ellipsoid("Pelican_Body", (0, 0, 0.5), (0.35, 0.7, 0.3))
if body:
    add_to_collection(body, body_brown)

# Breast/belly area
breast = create_ellipsoid("Pelican_Breast", (0, 0.15, 0.35), (0.28, 0.45, 0.22))
if breast:
    add_to_collection(breast, breast_white)

# Back hump
back = create_ellipsoid("Pelican_Back", (0, -0.1, 0.6), (0.25, 0.35, 0.18))
if back:
    add_to_collection(back, body_brown)

# === NECK - S-curved ===
neck_segments = []
neck_curve_points = [
    (0, 0.55, 0.55),
    (0, 0.7, 0.7),
    (0, 0.75, 0.9),
    (0, 0.65, 1.1),
    (0, 0.5, 1.25),
    (0, 0.45, 1.4),
]
neck_radii = [0.12, 0.10, 0.09, 0.085, 0.08, 0.075]

for i, (pos, rad) in enumerate(zip(neck_curve_points, neck_radii)):
    seg = create_ellipsoid(f"Neck_Seg_{i}", pos, (rad, rad*1.1, rad))
    if seg:
        add_to_collection(seg, neck_brown)
        neck_segments.append(seg)

# === HEAD ===
head = create_ellipsoid("Pelican_Head", (0, 0.42, 1.52), (0.09, 0.14, 0.1))
if head:
    add_to_collection(head, head_yellow)

# Crown/top of head
crown = create_ellipsoid("Pelican_Crown", (0, 0.38, 1.58), (0.07, 0.1, 0.06))
if crown:
    add_to_collection(crown, head_yellow)

# Nape (back of head, often darker in breeding)
nape = create_ellipsoid("Pelican_Nape", (0, 0.32, 1.5), (0.06, 0.08, 0.07))
if nape:
    add_to_collection(nape, neck_brown)

# === BILL - Upper mandible ===
bm = bmesh.new()
bill_length = 0.9
bill_verts = []
bill_profile_top = [
    (0, 0, 0),
    (0.04, 0.3, -0.02),
    (0.035, 0.6, -0.03),
    (0.02, 0.85, -0.02),
    (0.005, 0.9, 0),
]
bill_profile_bottom = [
    (0, 0, -0.03),
    (0.035, 0.3, -0.05),
    (0.03, 0.6, -0.055),
    (0.015, 0.85, -0.04),
    (0.005, 0.9, -0.02),
]

for x, y, z in bill_profile_top:
    bill_verts.append(bm.verts.new((x, y, z)))
    if x > 0:
        bill_verts.append(bm.verts.new((-x, y, z)))

for x, y, z in bill_profile_bottom:
    bill_verts.append(bm.verts.new((x, y, z)))
    if x > 0:
        bill_verts.append(bm.verts.new((-x, y, z)))

bm.verts.ensure_lookup_table()
bmesh.ops.convex_hull(bm, input=bm.verts)

bill_mesh = bpy.data.meshes.new("Upper_Bill_Mesh")
bm.to_mesh(bill_mesh)
bm.free()
upper_bill = bpy.data.objects.new("Upper_Bill", bill_mesh)
bpy.context.collection.objects.link(upper_bill)
upper_bill.location = (0, 0.52, 1.48)
upper_bill.rotation_euler = (math.radians(-5), 0, 0)
add_to_collection(upper_bill, bill_gray)

# Hook at tip of bill
bpy.ops.mesh.primitive_uv_sphere_add(segments=8, ring_count=6, radius=0.012, location=(0, 1.38, 1.44))
hook = bpy.context.active_object
if hook:
    hook.name = "Bill_Hook"
    hook.scale = (1, 1.5, 0.8)
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    add_to_collection(hook, bill_gray)

# === LOWER BILL & POUCH ===
bm = bmesh.new()
lower_bill_verts = []

pouch_profile = [
    (0, 0, 0),
    (0.03, 0.1, -0.01),
    (0.05, 0.3, -0.15),
    (0.06, 0.5, -0.2),
    (0.04, 0.7, -0.12),
    (0.02, 0.85, -0.04),
    (0.005, 0.9, -0.02),
]

for x, y, z in pouch_profile:
    lower_bill_verts.append(bm.verts.new((x, y, z)))
    if x > 0:
        lower_bill_verts.append(bm.verts.new((-x, y, z)))

center_bottom = [
    (0, 0.2, -0.18),
    (0, 0.4, -0.25),
    (0, 0.6, -0.22),
]
for pos in center_bottom:
    lower_bill_verts.append(bm.verts.new(pos))

bm.verts.ensure_lookup_table()
bmesh.ops.convex_hull(bm, input=bm.verts)

lower_mesh = bpy.data.meshes.new("Lower_Bill_Mesh")
bm.to_mesh(lower_mesh)
bm.free()
lower_bill = bpy.data.objects.new("Lower_Bill", lower_mesh)
bpy.context.collection.objects.link(lower_bill)
lower_bill.location = (0, 0.52, 1.45)
lower_bill.rotation_euler = (math.radians(-5), 0, 0)
add_to_collection(lower_bill, pouch_olive)

# Pouch interior hint
pouch_sac = create_ellipsoid("Pouch_Sac", (0, 0.85, 1.22), (0.045, 0.25, 0.12))
if pouch_sac:
    pouch_sac.rotation_euler = (math.radians(-10), 0, 0)
    add_to_collection(pouch_sac, pouch_olive)

# Lower bill bone ridges
for side in [-1, 1]:
    ridge = create_cylinder_from_cone(
        f"Bill_Ridge_{side}",
        (side * 0.025, 0.9, 1.42),
        0.008, 0.003, 0.7,
        segments=8,
        rotation=(math.radians(-95), 0, 0)
    )
    if ridge:
        add_to_collection(ridge, bill_gray)

# === EYES ===
for side in [-1, 1]:
    bpy.ops.mesh.primitive_uv_sphere_add(segments=16, ring_count=12, radius=0.025, location=(side * 0.07, 0.48, 1.54))
    eye = bpy.context.active_object
    if eye:
        eye.name = f"Eye_{side}"
        eye.scale = (0.6, 1, 1)
        bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
        add_to_collection(eye, eye_yellow)
    
    bpy.ops.mesh.primitive_uv_sphere_add(segments=12, ring_count=8, radius=0.012, location=(side * 0.085, 0.5, 1.54))
    pupil = bpy.context.active_object
    if pupil:
        pupil.name = f"Pupil_{side}"
        add_to_collection(pupil, pupil_dark)
    
    # Eye ring
    bpy.ops.mesh.primitive_torus_add(
        major_radius=0.028,
        minor_radius=0.004,
        location=(side * 0.075, 0.49, 1.54)
    )
    ring = bpy.context.active_object
    if ring:
        ring.name = f"Eye_Ring_{side}"
        ring.rotation_euler = (0, math.radians(90), 0)
        add_to_collection(ring, bill_gray)

# === WINGS ===
def create_wing(side):
    sign = 1 if side == "right" else -1
    wing_parts = []
    
    # Wing base (scapulars)
    scap = create_ellipsoid(f"Wing_Scapular_{side}", (sign * 0.25, -0.1, 0.55), (0.15, 0.35, 0.08))
    if scap:
        scap.rotation_euler = (0, 0, sign * math.radians(15))
        add_to_collection(scap, body_brown)
        wing_parts.append(scap)
    
    # Inner wing (secondaries)
    inner = create_ellipsoid(f"Wing_Inner_{side}", (sign * 0.5, -0.15, 0.5), (0.25, 0.3, 0.04))
    if inner:
        inner.rotation_euler = (0, 0, sign * math.radians(25))
        add_to_collection(inner, wing_dark)
        wing_parts.append(inner)
    
    # Mid wing
    mid = create_ellipsoid(f"Wing_Mid_{side}", (sign * 0.8, -0.2, 0.48), (0.22, 0.25, 0.03))
    if mid:
        mid.rotation_euler = (0, 0, sign * math.radians(30))
        add_to_collection(mid, wing_silver)
        wing_parts.append(mid)
    
    # Outer wing (primaries)
    outer = create_ellipsoid(f"Wing_Outer_{side}", (sign * 1.05, -0.25, 0.46), (0.18, 0.2, 0.025))
    if outer:
        outer.rotation_euler = (0, 0, sign * math.radians(35))
        add_to_collection(outer, wing_dark)
        wing_parts.append(outer)
    
    # Wing tip
    tip = create_ellipsoid(f"Wing_Tip_{side}", (sign * 1.2, -0.28, 0.45), (0.1, 0.15, 0.02))
    if tip:
        tip.rotation_euler = (0, 0, sign * math.radians(40))
        add_to_collection(tip, wing_dark)
        wing_parts.append(tip)
    
    # Primary feathers (folded against body)
    for i in range(6):
        t = i / 5.0
        x = sign * (0.35 + t * 0.85)
        y = -0.35 - t * 0.15
        z = 0.42 - t * 0.08
        
        feather = create_ellipsoid(
            f"Primary_{side}_{i}",
            (x, y, z),
            (0.02 + (1-t) * 0.02, 0.12 - t * 0.03, 0.008)
        )
        if feather:
            feather.rotation_euler = (math.radians(-5), 0, sign * math.radians(20 + t * 25))
            add_to_collection(feather, wing_dark)
            wing_parts.append(feather)
    
    # Covert feathers
    for i in range(4):
        t = i / 3.0
        x = sign * (0.3 + t * 0.5)
        y = -0.05 - t * 0.1
        z = 0.52 - t * 0.05
        
        covert = create_ellipsoid(
            f"Covert_{side}_{i}",
            (x, y, z),
            (0.06, 0.1, 0.015)
        )
        if covert:
            covert.rotation_euler = (0, 0, sign * math.radians(15 + t * 15))
            if t < 0.5:
                add_to_collection(covert, body_brown)
            else:
                add_to_collection(covert, wing_silver)
            wing_parts.append(covert)
    
    return wing_parts

create_wing("right")
create_wing("left")

# === TAIL ===
tail_base = create_ellipsoid("Tail_Base", (0, -0.65, 0.4), (0.12, 0.15, 0.08))
if tail_base:
    add_to_collection(tail_base, body_brown)

# Tail feathers
for i in range(8):
    angle = math.radians(-20 + i * 5)
    x = math.sin(angle) * 0.08
    y = -0.75 - abs(i - 3.5) * 0.02
    z = 0.38 + abs(i - 3.5) * 0.01
    
    feather = create_ellipsoid(
        f"Tail_Feather_{i}",
        (x, y, z),
        (0.02, 0.12, 0.008)
    )
    if feather:
        feather.rotation_euler = (math.radians(15), angle * 0.3, 0)
        add_to_collection(feather, wing_dark)

# Undertail coverts
undertail = create_ellipsoid("Undertail_Coverts", (0, -0.6, 0.32), (0.1, 0.1, 0.05))
if undertail:
    add_to_collection(undertail, breast_white)

# === LEGS ===
def create_leg(side):
    sign = 1 if side == "right" else -1
    leg_parts = []
    
    # Upper leg (thigh - mostly hidden)
    thigh = create_cylinder_from_cone(
        f"Thigh_{side}",
        (sign * 0.12, 0.1, 0.18),
        0.05, 0.04, 0.15,
        segments=12,
        rotation=(math.radians(70), sign * math.radians(10), 0)
    )
    if thigh:
        add_to_collection(thigh, body_brown)
        leg_parts.append(thigh)
    
    # Knee
    bpy.ops.mesh.primitive_uv_sphere_add(segments=12, ring_count=8, radius=0.035, location=(sign * 0.14, 0.15, 0.08))
    knee = bpy.context.active_object
    if knee:
        knee.name = f"Knee_{side}"
        add_to_collection(knee, leg_dark)
        leg_parts.append(knee)
    
    # Lower leg (tarsus)
    tarsus = create_cylinder_from_cone(
        f"Tarsus_{side}",
        (sign * 0.15, 0.18, -0.08),
        0.025, 0.02, 0.2,
        segments=10,
        rotation=(math.radians(100), sign * math.radians(5), 0)
    )
    if tarsus:
        add_to_collection(tarsus, leg_dark)
        leg_parts.append(tarsus)
    
    # Ankle joint
    bpy.ops.mesh.primitive_uv_sphere_add(segments=10, ring_count=8, radius=0.022, location=(sign * 0.16, 0.22, -0.18))
    ankle = bpy.context.active_object
    if ankle:
        ankle.name = f"Ankle_{side}"
        add_to_collection(ankle, leg_dark)
        leg_parts.append(ankle)
    
    # Foot base
    foot_base = create_ellipsoid(f"Foot_Base_{side}", (sign * 0.16, 0.28, -0.22), (0.04, 0.06, 0.015))
    if foot_base:
        add_to_collection(foot_base, leg_dark)
        leg_parts.append(foot_base)
    
    # Toes (4 webbed toes - totipalmate foot)
    toe_angles = [-35, -12, 12, 35]
    toe_lengths = [0.12, 0.15, 0.15, 0.12]
    
    for i, (toe_angle, toe_len) in enumerate(zip(toe_angles, toe_lengths)):
        rad_angle = math.radians(toe_angle)
        toe_x = sign * 0.16 + math.sin(rad_angle) * 0.02
        toe_y = 0.32 + math.cos(rad_angle) * toe_len * 0.5
        toe_z = -0.23
        
        toe = create_cylinder_from_cone(
            f"Toe_{side}_{i}",
            (toe_x, toe_y, toe_z),
            0.012, 0.006, toe_len,
            segments=8,
            rotation=(math.radians(85), 0, rad_angle)
        )
        if toe:
            add_to_collection(toe, leg_dark)
            leg_parts.append(toe)
        
        # Toe joints
        for j in range(2):
            joint_t = (j + 1) / 3.0
            jx = toe_x + math.sin(rad_angle) * toe_len * joint_t * 0.8
            jy = toe_y + math.cos(rad_angle) * toe_len * joint_t * 0.3
            jz = toe_z - 0.005
            
            bpy.ops.mesh.primitive_uv_sphere_add(segments=8, ring_count=6, radius=0.008, location=(jx, jy, jz))
            joint = bpy.context.active_object
            if joint:
                joint.name = f"Toe_Joint_{side}_{i}_{j}"
                add_to_collection(joint, leg_dark)
                leg_parts.append(joint)
        
        # Claw
        claw_x = toe_x + math.sin(rad_angle) * toe_len * 0.9
        claw_y = toe_y + toe_len * 0.4
        claw_z = toe_z - 0.01
        
        claw = create_cylinder_from_cone(
            f"Claw_{side}_{i}",
            (claw_x, claw_y, claw_z),
            0.006, 0.001, 0.025,
            segments=6,
            rotation=(math.radians(60), 0, rad_angle * 0.5)
        )
        if claw:
            add_to_collection(claw, claw_dark)
            leg_parts.append(claw)
    
    # Webbing between toes
    bm = bmesh.new()
    web_verts = []
    
    foot_center = (sign * 0.16, 0.32, -0.235)
    web_verts.append(bm.verts.new(foot_center))
    
    for i, toe_angle in enumerate(toe_angles):
        rad = math.radians(toe_angle)
        outer_x = foot_center[0] + math.sin(rad) * 0.1
        outer_y = foot_center[1] + math.cos(rad) * 0.12
        outer_z = foot_center[2] - 0.005
        web_verts.append(bm.verts.new((outer_x, outer_y, outer_z)))
    
    bm.verts.ensure_lookup_table()
    
    for i in range(len(toe_angles) - 1):
        try:
            bm.faces.new([web_verts[0], web_verts[i + 1], web_verts[i + 2]])
        except Exception:
            pass
    
    web_mesh = bpy.data.meshes.new(f"Webbing_{side}")
    bm.to_mesh(web_mesh)
    bm.free()
    
    webbing = bpy.data.objects.new(f"Webbing_{side}", web_mesh)
    bpy.context.collection.objects.link(webbing)
    add_to_collection(webbing, leg_dark)
    leg_parts.append(webbing)
    
    return leg_parts

create_leg("right")
create_leg("left")

# === ADDITIONAL DETAILS ===

# Nostril slits on bill
for side in [-1, 1]:
    bm = bmesh.new()
    bmesh.ops.create_cone(bm, cap_ends=True, cap_tris=False, segments=6, radius1=0.004, radius2=0.002, depth=0.015)
    nostril_mesh = bpy.data.meshes.new(f"Nostril_{side}")
    bm.to_mesh(nostril_mesh)
    bm.free()
    nostril = bpy.data.objects.new(f"Nostril_{side}", nostril_mesh)
    bpy.context.collection.objects.link(nostril)
    nostril.location = (side * 0.025, 0.62, 1.5)
    nostril.rotation_euler = (math.radians(90), 0, 0)
    add_to_collection(nostril, pupil_dark)

# Throat/chin area
chin = create_ellipsoid("Chin", (0, 0.5, 1.42), (0.06, 0.08, 0.05))
if chin:
    add_to_collection(chin, head_yellow)

# Gular skin connection
gular = create_ellipsoid("Gular_Connection", (0, 0.48, 1.35), (0.065, 0.12, 0.06))
if gular:
    add_to_collection(gular, pouch_olive)

# Cheek patches
for side in [-1, 1]:
    cheek = create_ellipsoid(f"Cheek_{side}", (side * 0.06, 0.42, 1.5), (0.025, 0.04, 0.03))
    if cheek:
        add_to_collection(cheek, head_yellow)

# Forehead
forehead = create_ellipsoid("Forehead", (0, 0.45, 1.56), (0.055, 0.06, 0.04))
if forehead:
    add_to_collection(forehead, head_yellow)

# Breeding plumage - dark hindneck stripe
hindneck = create_ellipsoid("Hindneck_Stripe", (0, 0.28, 1.45), (0.05, 0.15, 0.045))
if hindneck:
    add_to_collection(hindneck, neck_brown)

# Rump
rump = create_ellipsoid("Rump", (0, -0.55, 0.45), (0.18, 0.12, 0.1))
if rump:
    add_to_collection(rump, body_brown)

# Vent area
vent = create_ellipsoid("Vent", (0, -0.45, 0.28), (0.1, 0.08, 0.06))
if vent:
    add_to_collection(vent, breast_white)

# Flank feathers
for side in [-1, 1]:
    flank = create_ellipsoid(f"Flank_{side}", (side * 0.22, 0.0, 0.35), (0.08, 0.2, 0.1))
    if flank:
        add_to_collection(flank, body_brown)

# === JOIN ALL PARTS ===
bpy.ops.object.select_all(action='DESELECT')

valid_parts = []
for part_name in all_parts:
    obj = bpy.data.objects.get(part_name)
    if obj:
        obj.select_set(True)
        valid_parts.append(obj)

if valid_parts:
    bpy.context.view_layer.objects.active = valid_parts[0]
    if len(valid_parts) > 1:
        bpy.ops.object.join()
    
    pelican = bpy.context.active_object
    if pelican:
        pelican.name = "Brown_Pelican"
        
        # Center origin
        bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN')
        
        # Smooth shading
        bpy.ops.object.shade_smooth()
        
        # Add subdivision surface for smoother result
        try:
            subsurf = pelican.modifiers.new(name="Subdivision", type='SUBSURF')
            subsurf.levels = 1
            subsurf.render_levels = 2
        except Exception:
            pass

# Final positioning
pelican_final = bpy.data.objects.get("Brown_Pelican")
if pelican_final:
    # Position at ground level
    pelican_final.location = (0, 0, 0.25)
    pelican_final.rotation_euler = (0, 0, 0)
    
    bpy.ops.object.select_all(action='DESELECT')
    pelican_final.select_set(True)
    bpy.context.view_layer.objects.active = pelican_final