import bpy, bmesh, math

scene = bpy.context.scene
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
for block in (bpy.data.meshes, bpy.data.materials, bpy.data.curves):
    for b in list(block):
        if b.users == 0: block.remove(b)

def new_obj(name, mesh):
    o = bpy.data.objects.new(name, mesh)
    bpy.context.collection.objects.link(o)
    return o

# ---------- S-curve guide ----------
cu = bpy.data.curves.new('SCurve','CURVE')
cu.dimensions='3D'
sp = cu.splines.new('BEZIER'); sp.bezier_points.add(3)
pts = [(0.95,0,0),(0.32,0.14,0),(-0.32,-0.14,0),(-0.95,0,0)]
for bp,p in zip(sp.bezier_points, pts):
    bp.co = p; bp.handle_left_type=bp.handle_right_type='AUTO'
curve_obj = new_obj('SCurveGuide', cu)

def add_scurve(o):
    m = o.modifiers.new('S','CURVE')
    m.object = curve_obj
    m.deform_axis = 'POS_X'

bpy.ops.mesh.primitive_uv_sphere_add(segments=48, ring_count=32, radius=1)
body = bpy.context.active_object; body.name='GoldfishBody'
body.scale=(1.15,0.62,0.55)
bpy.ops.object.transform_apply(scale=True)
bm = bmesh.new(); bm.from_mesh(body.data)
for v in bm.verts:
    t = v.co.x
    if t < 0:
        f = max(0.12, 1.0 - (-t)**1.6*0.85)
        v.co.y*=f; v.co.z*=f
    else:
        f = 1.0 - t**2*0.35
        v.co.y*=f; v.co.z*=f
bm.to_mesh(body.data); bm.free()
for p in body.data.polygons: p.use_smooth=True
add_scurve(body)

def fin_mesh(name, pts):
    bm = bmesh.new()
    vs=[bm.verts.new(p) for p in pts]
    bm.faces.new(vs)
    bm.normal_update()
    me=bpy.data.meshes.new(name); bm.to_mesh(me); bm.free()
    return new_obj(name, me)

tail_pts = [(0,0.16,0),(-0.55,0.52,0),(-1.05,0.66,0),(-1.42,0.42,0),
            (-1.18,0.08,0),(-1.45,-0.28,0),(-1.02,-0.58,0),(-0.5,-0.44,0),
            (0,-0.16,0)]
tail = fin_mesh('TailFin', tail_pts)
tail.location=(-1.0,0,0)
sol = tail.modifiers.new('Sol','SOLIDIFY'); sol.thickness=0.03; sol.offset=0
sub = tail.modifiers.new('Sub','SUBSURF'); sub.levels=sub.render_levels=3
add_scurve(tail)

dor_pts = [(0.35,0,0.30),(0.1,0,0.72),(-0.25,0,0.78),(-0.55,0,0.5),(-0.75,0,0.34)]
dor = fin_mesh('DorsalFin', dor_pts)
sol = dor.modifiers.new('Sol','SOLIDIFY'); sol.thickness=0.025; sol.offset=0
sub = dor.modifiers.new('Sub','SUBSURF'); sub.levels=sub.render_levels=3
add_scurve(dor)

pec_pts = [(0,0,0),(0.38,0.30,0),(0.55,0.10,0),(0.30,-0.12,0)]
pec_r = fin_mesh('PecFinR', pec_pts)
pec_l = fin_mesh('PecFinL', [(-p[0],-p[1],p[2]) for p in pec_pts])
for p,rot,loc in ((pec_r,(math.radians(20),math.radians(70),0),(0.62,-0.52,-0.12)),
                  (pec_l,(math.radians(-20),math.radians(110),0),(0.62,0.52,-0.12))):
    p.rotation_euler=rot; p.location=loc
    sol=p.modifiers.new('Sol','SOLIDIFY'); sol.thickness=0.02; sol.offset=0
    s=p.modifiers.new('Sub','SUBSURF'); s.levels=s.render_levels=3

eye_mat = bpy.data.materials.new('EyeBlack'); eye_mat.use_nodes=True
pb = eye_mat.node_tree.nodes["Principled BSDF"]
pb.inputs['Base Color'].default_value=(0.01,0.01,0.01,1)
pb.inputs['Roughness'].default_value=0.08
for side,sign in (('R',1),('L',-1)):
    bpy.ops.mesh.primitive_uv_sphere_add(segments=24,ring_count=16,radius=0.09,
        location=(0.80, sign*0.50, 0.12))
    e=bpy.context.active_object; e.name='Eye'+side
    e.data.materials.append(eye_mat)

result={'ok':True}