import bpy
import bmesh
import math
import random
from mathutils import Vector

def clear_scene():
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete(use_global=False)
    for mesh in bpy.data.meshes:
        bpy.data.meshes.remove(mesh)
    for mat in bpy.data.materials:
        bpy.data.materials.remove(mat)

def create_material(name, base_color, roughness=0.5, subsurface=0.0):
    mat = bpy.data.materials.new(name=name)
    mat.use_nodes = True
    bsdf = mat.node_tree.nodes.get("Principled BSDF")
    if bsdf is None:
        return mat
    try:
        bsdf.inputs['Base Color'].default_value = tuple(base_color)
    except Exception:
        pass
    try:
        bsdf.inputs['Roughness'].default_value = float(roughness)
    except Exception:
        pass
    try:
        if 'Subsurface Weight' in bsdf.inputs:
            bsdf.inputs['Subsurface Weight'].default_value = float(subsurface)
    except Exception:
        pass
    return mat

def create_cap():
    bpy.ops.mesh.primitive_uv_sphere_add(segments=64, ring_count=32, radius=1.0, location=(0, 0, 4.0))
    cap = bpy.context.active_object
    cap.name = "Cap"
    bpy.ops.object.mode_set(mode='EDIT')
    bm = bmesh.from_edit_mesh(cap.data)
    bm.verts.ensure_lookup_table()
    for v in bm.verts:
        oz = v.co.z
        r = math.sqrt(v.co.x ** 2 + v.co.y ** 2)
        if oz < 0:
            t = abs(oz)
            v.co.z = -0.25 * t
            curve = 0.35 * (r / 1.0) * t
            v.co.z -= curve
            if r > 0.85 and t < 0.4:
                v.co.z += 0.08 * (1 - t / 0.4)
        else:
            v.co.z = oz * 0.55
            if r < 0.15:
                v.co.z += 0.06 * (1 - r / 0.15)
    bmesh.update_edit_mesh(cap.data)
    bpy.ops.object.mode_set(mode='OBJECT')
    cap.scale = (1.6, 1.6, 1.0)
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    for p in cap.data.polygons:
        p.use_smooth = True
    mat = create_material("CapRed", (0.78, 0.06, 0.02, 1.0), 0.38, 0.12)
    cap.data.materials.append(mat)
    return cap

def create_warts(cap_obj, count=24):
    warts = []
    mat = create_material("WartWhite", (0.97, 0.96, 0.93, 1.0), 0.55, 0.08)
    placed = []
    random.seed(12345)
    for i in range(count):
        for _ in range(60):
            theta = random.uniform(0, 2 * math.pi)
            phi = random.uniform(0.12, 0.78)
            rx = 1.6 * math.sin(phi)
            x = rx * math.cos(theta)
            y = rx * math.sin(theta)
            zs = math.cos(phi)
            z = 4.0 + zs * 0.55
            pos = Vector((x, y, z))
            ok = True
            for p2 in placed:
                if (pos - p2).length < 0.28:
                    ok = False
                    break
            if ok:
                placed.append(pos)
                break
        else:
            continue
        sz = random.uniform(0.055, 0.13)
        bpy.ops.mesh.primitive_uv_sphere_add(segments=10, ring_count=8, radius=sz, location=(x, y, z + sz * 0.25))
        w = bpy.context.active_object
        w.name = f"Wart{i:02d}"
        w.scale.z = random.uniform(0.28, 0.48)
        w.scale.x *= random.uniform(0.75, 1.25)
        w.scale.y *= random.uniform(0.75, 1.25)
        w.rotation_euler = (random.uniform(-0.15, 0.15), random.uniform(-0.15, 0.15), random.uniform(0, 6.28))
        for p in w.data.polygons:
            p.use_smooth = True
        w.data.materials.append(mat)
        warts.append(w)
    return warts

def create_gills(num=80):
    gills = []
    mat = create_material("GillCream", (0.98, 0.95, 0.89, 1.0), 0.72, 0.18)
    inner_r = 0.28
    outer_r = 1.48
    top_z = 3.72
    for i in range(num):
        angle = 2 * math.pi * i / num
        verts = []
        faces = []
        segs = 10
        for j in range(segs + 1):
            t = j / segs
            r = inner_r + t * (outer_r - inner_r)
            zt = top_z - 0.22 * (t ** 1.6)
            h = 0.22 + 0.28 * t
            cx = r * math.cos(angle)
            cy = r * math.sin(angle)
            verts.append((cx, cy, zt))
            verts.append((cx, cy, zt - h))
        for j in range(segs):
            b = j * 2
            faces.append((b, b + 1, b + 3, b + 2))
        mesh = bpy.data.meshes.new(f"GillMesh{i:02d}")
        mesh.from_pydata(verts, [], faces)
        mesh.update()
        obj = bpy.data.objects.new(f"Gill{i:02d}", mesh)
        bpy.context.collection.objects.link(obj)
        mod = obj.modifiers.new("Solid", 'SOLIDIFY')
        mod.thickness = 0.006
        mod.offset = 0
        for p in obj.data.polygons:
            p.use_smooth = True
        obj.data.materials.append(mat)
        gills.append(obj)
    return gills

def create_stem():
    bpy.ops.mesh.primitive_cylinder_add(vertices=32, radius=0.22, depth=4.5, location=(0, 0, 1.6))
    stem = bpy.context.active_object
    stem.name = "Stem"
    bpy.ops.object.mode_set(mode='EDIT')
    bm = bmesh.from_edit_mesh(stem.data)
    bm.verts.ensure_lookup_table()
    for v in bm.verts:
        vz = v.co.z
        rd = math.sqrt(v.co.x ** 2 + v.co.y ** 2)
        if rd < 0.001:
            continue
        if vz < -1.4:
            t = (-1.4 - vz) / 0.85
            sc = 1.0 + 1.8 * (t ** 0.7)
            v.co.x *= sc
            v.co.y *= sc
        elif vz > 1.5:
            t = (vz - 1.5) / 0.75
            sc = 1.0 - 0.12 * t
            v.co.x *= sc
            v.co.y *= sc
        ang = math.atan2(v.co.y, v.co.x)
        nz = math.sin(vz * 18 + ang * 4) * 0.008
        v.co.x += nz * math.cos(ang)
        v.co.y += nz * math.sin(ang)
    bmesh.update_edit_mesh(stem.data)
    bpy.ops.object.mode_set(mode='OBJECT')
    for p in stem.data.polygons:
        p.use_smooth = True
    mat = create_material("StemWhite", (0.96, 0.94, 0.89, 1.0), 0.48, 0.1)
    stem.data.materials.append(mat)
    return stem

def create_annulus():
    bpy.ops.mesh.primitive_torus_add(major_radius=0.30, minor_radius=0.045, major_segments=48, minor_segments=10, location=(0, 0, 3.0))
    ring = bpy.context.active_object
    ring.name = "Annulus"
    bpy.ops.object.mode_set(mode='EDIT')
    bm = bmesh.from_edit_mesh(ring.data)
    bm.verts.ensure_lookup_table()
    for v in bm.verts:
        rd = math.sqrt(v.co.x ** 2 + v.co.y ** 2)
        ang = math.atan2(v.co.y, v.co.x)
        if rd > 0.28:
            droop = (rd - 0.28) * 0.6
            v.co.z -= droop
        v.co.z += 0.012 * math.sin(ang * 14)
        v.co.x *= 1.25
        v.co.y *= 1.25
    bmesh.update_edit_mesh(ring.data)
    bpy.ops.object.mode_set(mode='OBJECT')
    for p in ring.data.polygons:
        p.use_smooth = True
    mat = create_material("AnnulusWhite", (0.97, 0.95, 0.91, 1.0), 0.52, 0.05)
    ring.data.materials.append(mat)
    return ring

def create_volva_remnants():
    parts = []
    mat = create_material("VolvaWhite", (0.93, 0.91, 0.86, 1.0), 0.62, 0.04)
    random.seed(7777)
    for ri in range(4):
        z_base = -0.5 - ri * 0.22
        rad = 0.52 + ri * 0.18
        n = 10 + ri * 3
        for i in range(n):
            ang = 2 * math.pi * i / n + ri * 0.18
            x = rad * math.cos(ang)
            y = rad * math.sin(ang)
            bpy.ops.mesh.primitive_uv_sphere_add(segments=8, ring_count=6, radius=0.065, location=(x, y, z_base))
            sc = bpy.context.active_object
            sc.name = f"VolvaScale{ri}_{i}"
            sc.scale = (random.uniform(0.9, 1.3), random.uniform(0.7, 1.0), random.uniform(0.22, 0.38))
            sc.rotation_euler.z = ang + 1.57
            sc.rotation_euler.x = -0.35
            for p in sc.data.polygons:
                p.use_smooth = True
            sc.data.materials.append(mat)
            parts.append(sc)
    return parts

def create_cap_underside():
    bpy.ops.mesh.primitive_cylinder_add(vertices=64, radius=1.52, depth=0.08, location=(0, 0, 3.68))
    underside = bpy.context.active_object
    underside.name = "CapUnderside"
    bpy.ops.object.mode_set(mode='EDIT')
    bm = bmesh.from_edit_mesh(underside.data)
    bm.verts.ensure_lookup_table()
    for v in bm.verts:
        rd = math.sqrt(v.co.x ** 2 + v.co.y ** 2)
        if v.co.z < 0:
            curve = 0.18 * (1 - (rd / 1.52) ** 1.5)
            v.co.z -= curve
        if rd < 0.32:
            v.co.z += 0.06 * (1 - rd / 0.32)
    bmesh.update_edit_mesh(underside.data)
    bpy.ops.object.mode_set(mode='OBJECT')
    for p in underside.data.polygons:
        p.use_smooth = True
    mat = create_material("UndersideCream", (0.98, 0.96, 0.91, 1.0), 0.65, 0.1)
    underside.data.materials.append(mat)
    return underside

def create_cap_margin():
    bpy.ops.mesh.primitive_torus_add(major_radius=1.55, minor_radius=0.06, major_segments=64, minor_segments=8, location=(0, 0, 3.52))
    margin = bpy.context.active_object
    margin.name = "CapMargin"
    bpy.ops.object.mode_set(mode='EDIT')
    bm = bmesh.from_edit_mesh(margin.data)
    bm.verts.ensure_lookup_table()
    for v in bm.verts:
        rd = math.sqrt(v.co.x ** 2 + v.co.y ** 2)
        if rd > 1.55:
            v.co.z -= 0.04
        v.co.z *= 0.6
    bmesh.update_edit_mesh(margin.data)
    bpy.ops.object.mode_set(mode='OBJECT')
    for p in margin.data.polygons:
        p.use_smooth = True
    mat = create_material("MarginRed", (0.75, 0.05, 0.02, 1.0), 0.4, 0.1)
    margin.data.materials.append(mat)
    return margin

def create_partial_veil_remnants():
    remnants = []
    mat = create_material("VeilWhite", (0.96, 0.94, 0.90, 1.0), 0.58, 0.06)
    random.seed(9999)
    for i in range(8):
        ang = 2 * math.pi * i / 8 + random.uniform(-0.1, 0.1)
        r = 1.42
        x = r * math.cos(ang)
        y = r * math.sin(ang)
        z = 3.48
        bpy.ops.mesh.primitive_uv_sphere_add(segments=6, ring_count=4, radius=0.035, location=(x, y, z))
        rem = bpy.context.active_object
        rem.name = f"VeilRemnant{i}"
        rem.scale = (random.uniform(0.8, 1.2), random.uniform(0.6, 1.0), random.uniform(0.3, 0.5))
        rem.rotation_euler = (random.uniform(-0.2, 0.2), random.uniform(-0.2, 0.2), ang)
        for p in rem.data.polygons:
            p.use_smooth = True
        rem.data.materials.append(mat)
        remnants.append(rem)
    return remnants

def create_spore_print_hint():
    bpy.ops.mesh.primitive_plane_add(size=0.5, location=(0.8, 0.3, -0.68))
    sp = bpy.context.active_object
    sp.name = "SporeHint"
    sp.rotation_euler.z = 0.4
    mat = create_material("SporeWhite", (0.99, 0.98, 0.96, 1.0), 0.85, 0.0)
    sp.data.materials.append(mat)
    return sp

def create_mycelium_base():
    parts = []
    mat = create_material("MyceliumWhite", (0.94, 0.92, 0.88, 1.0), 0.7, 0.05)
    random.seed(3333)
    for i in range(15):
        ang = random.uniform(0, 2 * math.pi)
        dist = random.uniform(0.3, 0.8)
        x = dist * math.cos(ang)
        y = dist * math.sin(ang)
        z = -0.7 - random.uniform(0, 0.4)
        bpy.ops.mesh.primitive_cylinder_add(vertices=8, radius=random.uniform(0.015, 0.035), depth=random.uniform(0.3, 0.7), location=(x, y, z))
        strand = bpy.context.active_object
        strand.name = f"Mycelium{i}"
        strand.rotation_euler = (random.uniform(-0.4, 0.4), random.uniform(-0.4, 0.4), random.uniform(0, 6.28))
        for p in strand.data.polygons:
            p.use_smooth = True
        strand.data.materials.append(mat)
        parts.append(strand)
    return parts

def create_basal_bulb_detail():
    bpy.ops.mesh.primitive_torus_add(major_radius=0.55, minor_radius=0.03, major_segments=32, minor_segments=8, location=(0, 0, -0.65))
    ring1 = bpy.context.active_object
    ring1.name = "BasalRing1"
    for p in ring1.data.polygons:
        p.use_smooth = True
    mat = create_material("BasalRingWhite", (0.92, 0.90, 0.85, 1.0), 0.6, 0.04)
    ring1.data.materials.append(mat)
    bpy.ops.mesh.primitive_torus_add(major_radius=0.62, minor_radius=0.025, major_segments=32, minor_segments=8, location=(0, 0, -0.85))
    ring2 = bpy.context.active_object
    ring2.name = "BasalRing2"
    for p in ring2.data.polygons:
        p.use_smooth = True
    ring2.data.materials.append(mat)
    return [ring1, ring2]

def main():
    clear_scene()
    all_objects = []
    cap = create_cap()
    all_objects.append(cap)
    warts = create_warts(cap, count=26)
    all_objects.extend(warts)
    cap_underside = create_cap_underside()
    all_objects.append(cap_underside)
    cap_margin = create_cap_margin()
    all_objects.append(cap_margin)
    gills = create_gills(num=88)
    all_objects.extend(gills)
    stem = create_stem()
    all_objects.append(stem)
    annulus = create_annulus()
    all_objects.append(annulus)
    volva = create_volva_remnants()
    all_objects.extend(volva)
    veil_remnants = create_partial_veil_remnants()
    all_objects.extend(veil_remnants)
    mycelium = create_mycelium_base()
    all_objects.extend(mycelium)
    basal_rings = create_basal_bulb_detail()
    all_objects.extend(basal_rings)
    bpy.ops.object.empty_add(type='PLAIN_AXES', location=(0, 0, 0))
    parent_empty = bpy.context.active_object
    parent_empty.name = "Amanita_muscaria"
    for obj in all_objects:
        if obj is not None:
            try:
                obj.parent = parent_empty
            except Exception:
                pass
    bpy.ops.object.select_all(action='DESELECT')
    parent_empty.select_set(True)
    bpy.context.view_layer.objects.active = parent_empty

if __name__ == "__main__":
    main()