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

# ---------------------------------------------------------------------------
# Remove default mesh objects (e.g. startup cube) so only the stork remains
# ---------------------------------------------------------------------------
try:
    for ob in list(bpy.data.objects):
        if ob.type == 'MESH':
            bpy.data.objects.remove(ob, do_unlink=True)
except Exception:
    pass

# ---------------------------------------------------------------------------
# Material indices
# ---------------------------------------------------------------------------
WHITE, BLACK, BEAK, LEG, IRIS, PUPIL, SKIN, CLAW = range(8)


class Builder:
    def __init__(self):
        self.verts = []
        self.faces = []
        self.mats = []

    def add(self, verts, faces, mat):
        off = len(self.verts)
        self.verts.extend([Vector(v) for v in verts])
        for f in faces:
            self.faces.append(tuple(i + off for i in f))
            self.mats.append(mat)


B = Builder()


def _rad(r):
    if isinstance(r, (int, float)):
        return (float(r), float(r))
    return (float(r[0]), float(r[1]))


def catmull(pts, vals, sub):
    n = len(pts)
    out_p, out_v = [], []
    for i in range(n - 1):
        p0 = pts[max(i - 1, 0)]
        p1 = pts[i]
        p2 = pts[i + 1]
        p3 = pts[min(i + 2, n - 1)]
        v0, v1 = vals[i], vals[i + 1]
        for k in range(sub):
            t = k / sub
            t2 = t * t
            t3 = t2 * t
            p = 0.5 * ((2.0 * p1) + (p2 - p0) * t +
                       (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 +
                       (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3)
            out_p.append(p)
            out_v.append((v0[0] + (v1[0] - v0[0]) * t, v0[1] + (v1[1] - v0[1]) * t))
    out_p.append(pts[-1].copy())
    out_v.append(vals[-1])
    return out_p, out_v


def tube(b, pts, radii, mat, segs=12, sub=4, up=(0, 0, 1), cap0=True, cap1=True):
    pts = [Vector(p) for p in pts]
    radii = [_rad(r) for r in radii]
    if sub > 1 and len(pts) >= 2:
        pts, radii = catmull(pts, radii, sub)
    upv = Vector(up).normalized()
    n = len(pts)
    verts, faces = [], []
    for i in range(n):
        if i == 0:
            t = pts[1] - pts[0]
        elif i == n - 1:
            t = pts[-1] - pts[-2]
        else:
            t = pts[i + 1] - pts[i - 1]
        if t.length < 1e-9:
            t = Vector((1, 0, 0))
        t.normalize()
        side = t.cross(upv)
        if side.length < 1e-6:
            side = t.cross(Vector((1, 0, 0)))
            if side.length < 1e-6:
                side = t.cross(Vector((0, 1, 0)))
        side.normalize()
        u = side.cross(t).normalized()
        ra, rb = radii[i]
        ra = max(ra, 0.0003)
        rb = max(rb, 0.0003)
        for j in range(segs):
            a = 2.0 * math.pi * j / segs
            verts.append(pts[i] + side * (ra * math.cos(a)) + u * (rb * math.sin(a)))
    for i in range(n - 1):
        for j in range(segs):
            j1 = (j + 1) % segs
            faces.append((i * segs + j, i * segs + j1, (i + 1) * segs + j1, (i + 1) * segs + j))
    if cap0:
        c = len(verts)
        verts.append(pts[0].copy())
        for j in range(segs):
            faces.append((c, (j + 1) % segs, j))
    if cap1:
        c = len(verts)
        verts.append(pts[-1].copy())
        base = (n - 1) * segs
        for j in range(segs):
            faces.append((c, base + j, base + (j + 1) % segs))
    b.add(verts, faces, mat)


def ellipsoid(b, center, radii, mat, rot=None, segs=24, rings=14, deform=None):
    c = Vector(center)
    R = rot if rot is not None else Matrix.Identity(3)
    unit = [Vector((0, 0, 1))]
    for i in range(1, rings):
        th = math.pi * i / rings
        for j in range(segs):
            ph = 2.0 * math.pi * j / segs
            unit.append(Vector((math.sin(th) * math.cos(ph),
                                math.sin(th) * math.sin(ph),
                                math.cos(th))))
    unit.append(Vector((0, 0, -1)))
    out = []
    for v in unit:
        p = Vector((v.x * radii[0], v.y * radii[1], v.z * radii[2]))
        if deform is not None:
            p = deform(v, p)
        out.append(c + R @ p)
    faces = []
    top = 0
    bot = len(unit) - 1
    for j in range(segs):
        faces.append((top, 1 + j, 1 + (j + 1) % segs))
    for i in range(rings - 2):
        r0 = 1 + i * segs
        r1 = 1 + (i + 1) * segs
        for j in range(segs):
            j1 = (j + 1) % segs
            faces.append((r0 + j, r1 + j, r1 + j1, r0 + j1))
    last = 1 + (rings - 2) * segs
    for j in range(segs):
        faces.append((bot, last + (j + 1) % segs, last + j))
    b.add(out, faces, mat)


def slab(b, p0, p1, p2, thick, mat):
    p0, p1, p2 = Vector(p0), Vector(p1), Vector(p2)
    n = (p1 - p0).cross(p2 - p0)
    if n.length < 1e-9:
        return
    n = n.normalized() * (thick * 0.5)
    v = [p0 + n, p1 + n, p2 + n, p0 - n, p1 - n, p2 - n]
    f = [(0, 1, 2), (3, 5, 4), (0, 3, 4, 1), (1, 4, 5, 2), (2, 5, 3, 0)]
    b.add(v, f, mat)


def rotY(deg):
    return Matrix.Rotation(math.radians(deg), 3, 'Y')


def rotZ(deg):
    return Matrix.Rotation(math.radians(deg), 3, 'Z')


def rotX(deg):
    return Matrix.Rotation(math.radians(deg), 3, 'X')


# ---------------------------------------------------------------------------
# BODY (stork faces +X, Z up, left side = +Y)
# ---------------------------------------------------------------------------
def body_deform(v, p):
    xn = v.x
    if xn < 0:
        f = 1.0 - 0.32 * (xn * xn)
        p.y *= f
        p.z *= f
    if xn > 0 and v.z < 0:
        p.z *= 1.0 + 0.12 * xn
    if v.z > 0:
        p.z *= 0.92
    return p


ellipsoid(B, (0.0, 0.0, 0.66), (0.24, 0.125, 0.14), WHITE,
          rot=rotY(-12), segs=48, rings=32, deform=body_deform)

# Rump / uppertail coverts
ellipsoid(B, (-0.19, 0.0, 0.672), (0.09, 0.07, 0.045), WHITE,
          rot=rotY(8), segs=28, rings=16)

# ---------------------------------------------------------------------------
# NECK
# ---------------------------------------------------------------------------
neck_pts = [(0.12, 0, 0.72), (0.20, 0, 0.78), (0.245, 0, 0.86),
            (0.245, 0, 0.93), (0.262, 0, 0.99), (0.29, 0, 1.025)]
neck_r = [0.072, 0.058, 0.043, 0.035, 0.031, 0.028]
tube(B, neck_pts, neck_r, WHITE, segs=24, sub=5, up=(0, 1, 0))

# Elongated hanging foreneck / breast plumes
for layer in range(2):
    count = 11 if layer == 0 else 8
    for k in range(count):
        f = (k - (count - 1) / 2.0) / ((count - 1) / 2.0)
        yy = f * (0.036 if layer == 0 else 0.028)
        bz = 0.815 - 0.025 * layer + 0.006 * math.cos(k * 1.7)
        bx = 0.236 - 0.012 * abs(f) + 0.004 * layer
        L = 0.085 - 0.02 * abs(f) - 0.01 * layer
        base = Vector((bx, yy, bz))
        d = Vector((0.28, f * 0.08, -1.0)).normalized()
        mid = base + d * (L * 0.5) + Vector((0.012, 0, 0))
        tip = base + d * L + Vector((0.006, 0, 0))
        tube(B, [base, mid, tip],
             [(0.011, 0.0022), (0.010, 0.002), (0.0008, 0.0008)],
             WHITE, segs=8, sub=3, up=(1, 0, 0))

# ---------------------------------------------------------------------------
# HEAD
# ---------------------------------------------------------------------------
ellipsoid(B, (0.30, 0.0, 1.035), (0.058, 0.034, 0.040), WHITE,
          rot=rotY(8), segs=32, rings=20)

# Beak (long, straight, red): upper and lower mandibles
bd = math.radians(28)
bdir = Vector((math.cos(bd), 0, -math.sin(bd)))
bnrm = Vector((math.sin(bd), 0, math.cos(bd)))
bbase = Vector((0.33, 0.0, 1.024))
BL = 0.19
ts = [0.0, 0.15, 0.4, 0.7, 0.9, 1.0]
up_pts = [bbase + bdir * (BL * t) + bnrm * (0.006 * (1 - t) + 0.0005) for t in ts]
up_r = [(0.0135, 0.0145), (0.0125, 0.012), (0.0095, 0.0085),
        (0.006, 0.0055), (0.0035, 0.003), (0.0006, 0.0006)]
tube(B, up_pts, up_r, BEAK, segs=18, sub=4, up=(0, 0, 1))
LL = BL * 0.97
lo_pts = [bbase + bdir * (LL * t) - bnrm * (0.008 * (1 - t) + 0.001) for t in ts]
lo_r = [(0.0125, 0.011), (0.0115, 0.009), (0.009, 0.007),
        (0.0055, 0.0045), (0.003, 0.0025), (0.0006, 0.0006)]
tube(B, lo_pts, lo_r, BEAK, segs=18, sub=4, up=(0, 0, 1))

# Nostrils (slit-like)
for s in (1, -1):
    npos = bbase + bdir * 0.035 + bnrm * 0.0105 + Vector((0, s * 0.0116, 0))
    ellipsoid(B, npos, (0.007, 0.0015, 0.0017), PUPIL, rot=rotY(28), segs=10, rings=6)

# Eyes, black periorbital/loral skin, iris, pupil
for s in (1, -1):
    ellipsoid(B, (0.326, s * 0.0265, 1.039), (0.023, 0.0055, 0.0115), SKIN,
              segs=18, rings=10)
    ellipsoid(B, (0.318, s * 0.0285, 1.043), (0.0075, 0.0075, 0.0075), IRIS,
              segs=16, rings=10)
    ellipsoid(B, (0.3185, s * 0.0348, 1.043), (0.0035, 0.0018, 0.0035), PUPIL,
              segs=12, rings=8)

# ---------------------------------------------------------------------------
# WINGS (folded): white coverts, black secondaries and primaries
# ---------------------------------------------------------------------------
for s in (1, -1):
    # Black secondaries / greater coverts panel (lower, rear)
    ellipsoid(B, (-0.10, s * 0.106, 0.655), (0.20, 0.026, 0.075), BLACK,
              rot=rotZ(s * 7) @ rotY(-6), segs=32, rings=18)
    # White lesser/median coverts (upper, front)
    ellipsoid(B, (0.03, s * 0.118, 0.705), (0.17, 0.03, 0.085), WHITE,
              rot=rotZ(s * 6) @ rotY(-8), segs=32, rings=18)
    # Scapular feathers (white) along back edge
    ellipsoid(B, (-0.02, s * 0.075, 0.765), (0.14, 0.05, 0.035), WHITE,
              rot=rotZ(s * 5) @ rotY(-6), segs=24, rings=12)
    # Primaries: long black blades extending over tail
    for i in range(7):
        f = i / 6.0
        base = Vector((-0.02 - 0.10 * f, s * (0.112 - 0.002 * i), 0.662 - 0.025 * f))
        tip = Vector((-0.43 + 0.05 * f, s * (0.02 + 0.03 * f), 0.625 - 0.012 * f))
        m1 = base.lerp(tip, 0.4) + Vector((0, s * 0.012, 0.0))
        m2 = base.lerp(tip, 0.75) + Vector((0, s * 0.006, -0.004))
        tube(B, [base, m1, m2, tip],
             [(0.0035, 0.026), (0.0035, 0.025), (0.003, 0.017), (0.0012, 0.003)],
             BLACK, segs=10, sub=4, up=(0, 0, 1))

# ---------------------------------------------------------------------------
# TAIL: 12 white rectrices in a shallow fan
# ---------------------------------------------------------------------------
for i in range(12):
    f = (i - 5.5) / 5.5
    a = math.radians(180 + f * 22)
    d = Vector((math.cos(a), math.sin(a), 0))
    L = 0.22 - 0.03 * abs(f)
    base = Vector((-0.16, f * 0.02, 0.648 + 0.004 * (1 - abs(f))))
    pts = [base,
           base + d * (L * 0.35) + Vector((0, 0, -0.010)),
           base + d * (L * 0.75) + Vector((0, 0, -0.025)),
           base + d * L + Vector((0, 0, -0.035))]
    tube(B, pts, [(0.014, 0.004), (0.022, 0.0035), (0.020, 0.003), (0.004, 0.002)],
         WHITE, segs=10, sub=4, up=(0, 0, 1))

# ---------------------------------------------------------------------------
# LEGS and FEET (red)
# ---------------------------------------------------------------------------
for s in (1, -1):
    # Feathered thigh ("trousers")
    ellipsoid(B, (0.025, s * 0.068, 0.565), (0.055, 0.04, 0.065), WHITE,
              rot=rotY(-10), segs=24, rings=14)
    # Tibiotarsus (exposed lower part)
    tube(B, [(0.028, s * 0.066, 0.56), (0.018, s * 0.065, 0.45),
             (0.006, s * 0.063, 0.35), (-0.004, s * 0.062, 0.27)],
         [0.012, 0.0105, 0.0095, 0.0115], LEG, segs=14, sub=4, up=(1, 0, 0))
    # Intertarsal ("ankle") joint
    ellipsoid(B, (-0.006, s * 0.062, 0.27), (0.015, 0.012, 0.018), LEG,
              segs=16, rings=10)
    # Tarsometatarsus
    tube(B, [(-0.004, s * 0.062, 0.27), (0.0, s * 0.061, 0.18),
             (0.005, s * 0.06, 0.09), (0.01, s * 0.06, 0.03)],
         [(0.009, 0.012), (0.0075, 0.010), (0.0075, 0.0095), (0.009, 0.011)],
         LEG, segs=14, sub=4, up=(1, 0, 0))
    # Foot pad
    foot = Vector((0.012, s * 0.06, 0.02))
    ellipsoid(B, (0.01, s * 0.06, 0.018), (0.016, 0.013, 0.008), LEG,
              segs=16, rings=10)

    toe_dirs = {}
    toes = [("mid", 0.0, 0.095), ("outer", s * 30.0, 0.08), ("inner", -s * 24.0, 0.075)]
    for name, ang, L in toes:
        a = math.radians(ang)
        d = Vector((math.cos(a), math.sin(a), 0))
        toe_dirs[name] = (d, L)
        pts = [foot,
               foot + d * (L * 0.3) + Vector((0, 0, -0.006)),
               foot + d * (L * 0.65) + Vector((0, 0, -0.010)),
               foot + d * L + Vector((0, 0, -0.012))]
        tube(B, pts, [(0.0075, 0.0065), (0.0065, 0.0055), (0.0055, 0.005), (0.0042, 0.004)],
             LEG, segs=10, sub=4, up=(0, 0, 1))
        tip = pts[-1]
        tube(B, [tip - d * 0.002, tip + d * 0.008 + Vector((0, 0, -0.002)),
                 tip + d * 0.014 + Vector((0, 0, -0.005))],
             [0.0035, 0.0022, 0.0004], CLAW, segs=8, sub=3, up=(0, 0, 1))

    # Hind toe (hallux)
    hd = Vector((-1.0, s * 0.15, 0)).normalized()
    hb = foot + Vector((-0.004, 0, 0.003))
    hpts = [hb, hb + hd * 0.02 + Vector((0, 0, -0.006)), hb + hd * 0.04 + Vector((0, 0, -0.013))]
    tube(B, hpts, [(0.006, 0.0055), (0.005, 0.0045), (0.0038, 0.0035)],
         LEG, segs=10, sub=4, up=(0, 0, 1))
    htip = hpts[-1]
    tube(B, [htip - hd * 0.002, htip + hd * 0.007 + Vector((0, 0, -0.002)),
             htip + hd * 0.012 + Vector((0, 0, -0.004))],
         [0.003, 0.002, 0.0004], CLAW, segs=8, sub=3, up=(0, 0, 1))

    # Partial basal webbing between front toes
    dm, Lm = toe_dirs["mid"]
    for other in ("outer", "inner"):
        do, Lo = toe_dirs[other]
        p0 = foot + dm * 0.004 + Vector((0, 0, -0.001))
        p1 = foot + dm * (Lm * 0.38) + Vector((0, 0, -0.006))
        p2 = foot + do * (Lo * 0.38) + Vector((0, 0, -0.006))
        slab(B, p0, p1, p2, 0.0015, LEG)

# ---------------------------------------------------------------------------
# Build single mesh object
# ---------------------------------------------------------------------------
me = bpy.data.meshes.new("WhiteStork_Mesh")
me.from_pydata([tuple(v) for v in B.verts], [], B.faces)
me.update()
try:
    me.polygons.foreach_set("material_index", B.mats)
except Exception:
    for p, m in zip(me.polygons, B.mats):
        p.material_index = m
me.update()

try:
    bm = bmesh.new()
    bm.from_mesh(me)
    bmesh.ops.recalc_face_normals(bm, faces=bm.faces[:])
    bm.to_mesh(me)
    bm.free()
except Exception:
    pass

try:
    me.shade_smooth()
except Exception:
    try:
        for p in me.polygons:
            p.use_smooth = True
    except Exception:
        pass

me.update()

obj = bpy.data.objects.new("WhiteStork_Ciconia_ciconia", me)
bpy.context.scene.collection.objects.link(obj)


# ---------------------------------------------------------------------------
# Materials (minimal, defensive)
# ---------------------------------------------------------------------------
def make_mat(name, color, rough=0.5):
    m = bpy.data.materials.new(name)
    try:
        m.diffuse_color = (color[0], color[1], color[2], 1.0)
    except Exception:
        pass
    try:
        m.use_nodes = True
        bsdf = m.node_tree.nodes.get("Principled BSDF")
        if bsdf is not None:
            try:
                bsdf.inputs["Base Color"].default_value = (color[0], color[1], color[2], 1.0)
            except Exception:
                pass
            try:
                bsdf.inputs["Roughness"].default_value = float(rough)
            except Exception:
                pass
    except Exception:
        pass
    return m


mats = [
    make_mat("Stork_WhiteFeathers", (0.88, 0.88, 0.86), 0.75),
    make_mat("Stork_BlackFlightFeathers", (0.015, 0.015, 0.018), 0.6),
    make_mat("Stork_RedBeak", (0.80, 0.10, 0.04), 0.35),
    make_mat("Stork_RedLegs", (0.72, 0.12, 0.08), 0.5),
    make_mat("Stork_Iris", (0.12, 0.05, 0.02), 0.2),
    make_mat("Stork_Pupil", (0.005, 0.005, 0.005), 0.1),
    make_mat("Stork_EyeSkin", (0.03, 0.02, 0.02), 0.45),
    make_mat("Stork_Claws", (0.12, 0.09, 0.08), 0.4),
]
for m in mats:
    me.materials.append(m)

try:
    bpy.ops.object.select_all(action='DESELECT')
except Exception:
    pass
try:
    bpy.context.view_layer.objects.active = obj
    obj.select_set(True)
except Exception:
    pass