from manim import *
import numpy as np
import random

class OSKVLogo3D(ThreeDScene):
    def construct(self):
        self.camera.background_color = "#050510"
        
        # === PART 1: 3D Camera Sweep with Rotating Geometry ===
        self.set_camera_orientation(phi=70 * DEGREES, theta=-45 * DEGREES)
        
        # Wireframe sphere
        sphere_wireframe = Surface(
            lambda u, v: np.array([
                2 * np.cos(u) * np.sin(v),
                2 * np.sin(u) * np.sin(v),
                2 * np.cos(v)
            ]),
            u_range=[0, TAU],
            v_range=[0, PI],
            resolution=(12, 6),
            fill_opacity=0.05,
            stroke_width=1,
            stroke_color=BLUE_B,
            stroke_opacity=0.6
        )
        
        # Inner sphere
        inner_sphere = Surface(
            lambda u, v: np.array([
                0.8 * np.cos(u) * np.sin(v),
                0.8 * np.sin(u) * np.sin(v),
                0.8 * np.cos(v)
            ]),
            u_range=[0, TAU],
            v_range=[0, PI],
            resolution=(8, 4),
            fill_opacity=0.1,
            fill_color=PURPLE,
            stroke_width=0.5,
            stroke_color=PURPLE_B,
            stroke_opacity=0.4
        )
        
        # Torus rings
        torus = Surface(
            lambda u, v: np.array([
                (2.5 + 0.12 * np.cos(v)) * np.cos(u),
                (2.5 + 0.12 * np.cos(v)) * np.sin(u),
                0.12 * np.sin(v)
            ]),
            u_range=[0, TAU],
            v_range=[0, TAU],
            resolution=(24, 6),
            fill_opacity=0.3,
            fill_color=TEAL,
            stroke_width=0.5,
            stroke_color=TEAL,
            stroke_opacity=0.5
        )
        
        torus2 = Surface(
            lambda u, v: np.array([
                0.12 * np.sin(v),
                (2.5 + 0.12 * np.cos(v)) * np.cos(u),
                (2.5 + 0.12 * np.cos(v)) * np.sin(u),
            ]),
            u_range=[0, TAU],
            v_range=[0, TAU],
            resolution=(24, 6),
            fill_opacity=0.3,
            fill_color=BLUE,
            stroke_width=0.5,
            stroke_color=BLUE,
            stroke_opacity=0.5
        )
        
        # 3D floating dots
        dots_3d = VGroup()
        for _ in range(40):
            r = random.uniform(3, 5)
            theta_r = random.uniform(0, TAU)
            phi_r = random.uniform(0, PI)
            dot = Dot3D(
                point=np.array([
                    r * np.sin(phi_r) * np.cos(theta_r),
                    r * np.sin(phi_r) * np.sin(theta_r),
                    r * np.cos(phi_r)
                ]),
                radius=random.uniform(0.03, 0.07),
                color=random.choice([BLUE, PURPLE, TEAL, WHITE])
            )
            dot.set_opacity(random.uniform(0.3, 0.7))
            dots_3d.add(dot)
        
        # Build scene
        self.play(Create(sphere_wireframe), run_time=1.5)
        self.play(
            Create(inner_sphere),
            FadeIn(dots_3d),
            run_time=1
        )
        self.play(Create(torus), Create(torus2), run_time=1.5)
        self.move_camera(theta=-20 * DEGREES, phi=60 * DEGREES, run_time=1)
        
        # Rotate everything
        all_3d = VGroup(sphere_wireframe, inner_sphere, torus, torus2, dots_3d)
        
        self.play(Rotate(all_3d, PI/3, axis=UP), run_time=1.5)
        self.move_camera(theta=30 * DEGREES, phi=50 * DEGREES, run_time=1)
        
        # === PART 2: Collapse to 2D ===
        self.play(all_3d.animate.scale(0.3).set_opacity(0.15), run_time=1)
        self.move_camera(theta=0, phi=0, run_time=0.8)
        
        # Flash
        flash = Rectangle(width=20, height=12, fill_color=WHITE, fill_opacity=0.7, stroke_width=0)
        self.play(FadeIn(flash, run_time=0.15))
        self.play(FadeOut(flash, run_time=0.3), FadeOut(all_3d, run_time=0.3))
        
        # === PART 3: Logo reveal ===
        # Since ImageMobject can be tricky in 3D, use it after camera is face-on
        logo = ImageMobject("/Users/jakeshore/.clawdbot/workspace/oskv_logo.png")
        logo.scale_to_fit_width(7)
        
        self.play(FadeIn(logo), run_time=0.8)
        
        # === PART 4: 3D cube wireframe around logo ===
        cube_size = 4.5
        vertices = [
            np.array([x, y, z]) * cube_size / 2
            for x in [-1, 1] for y in [-1, 1] for z in [-1, 1]
        ]
        edges_idx = [
            (0,1),(0,2),(0,4),(1,3),(1,5),(2,3),
            (2,6),(3,7),(4,5),(4,6),(5,7),(6,7)
        ]
        
        cube_edges = VGroup()
        for i, j in edges_idx:
            line = Line3D(start=vertices[i], end=vertices[j], thickness=0.01, color=BLUE_B)
            line.set_opacity(0.4)
            cube_edges.add(line)
        
        vertex_dots = VGroup()
        for v in vertices:
            dot = Dot3D(point=v, radius=0.06, color=TEAL)
            dot.set_opacity(0.7)
            vertex_dots.add(dot)
        
        cube_group = VGroup(cube_edges, vertex_dots)
        
        self.play(
            LaggedStart(*[Create(e) for e in cube_edges], lag_ratio=0.05),
            LaggedStart(*[GrowFromCenter(d) for d in vertex_dots], lag_ratio=0.05),
            run_time=1.5
        )
        
        # Rotate cube
        self.play(
            Rotate(cube_group, PI/4, axis=UP + RIGHT * 0.5),
            run_time=2
        )
        
        # === PART 5: Orbiting parametric paths ===
        orbit_ring = ParametricFunction(
            lambda t: np.array([5 * np.cos(t), 1.5 * np.sin(t), 0.8 * np.sin(t)]),
            t_range=[0, TAU],
            color=PURPLE,
            stroke_width=2,
            stroke_opacity=0.6
        )
        orbit_ring2 = ParametricFunction(
            lambda t: np.array([5 * np.cos(t), -0.8 * np.sin(t), 1.5 * np.sin(t)]),
            t_range=[0, TAU],
            color=TEAL,
            stroke_width=2,
            stroke_opacity=0.5
        )
        
        self.play(Create(orbit_ring), Create(orbit_ring2), run_time=1)
        
        # Energy dots
        energy_dots = VGroup()
        for i in range(4):
            t = i * TAU / 4
            dot = Dot3D(
                point=np.array([5*np.cos(t), 1.5*np.sin(t), 0.8*np.sin(t)]),
                radius=0.08, color=PURPLE
            )
            energy_dots.add(dot)
        
        energy_dots2 = VGroup()
        for i in range(4):
            t = i * TAU / 4
            dot = Dot3D(
                point=np.array([5*np.cos(t), -0.8*np.sin(t), 1.5*np.sin(t)]),
                radius=0.08, color=TEAL
            )
            energy_dots2.add(dot)
        
        self.play(FadeIn(energy_dots), FadeIn(energy_dots2), run_time=0.3)
        
        # Rotate energy + cube
        self.play(
            Rotate(energy_dots, PI/2, about_point=ORIGIN, axis=OUT),
            Rotate(energy_dots2, PI/2, about_point=ORIGIN, axis=UP),
            Rotate(cube_group, PI/6, axis=UP),
            run_time=1.5
        )
        
        # === PART 6: Pulse ===
        self.play(
            cube_group.animate.scale(1.1),
            rate_func=there_and_back,
            run_time=0.7
        )
        
        # Ring wave
        ring3d = ParametricFunction(
            lambda t: np.array([0.5*np.cos(t), 0.5*np.sin(t), 0]),
            t_range=[0, TAU],
            color=WHITE,
            stroke_width=3,
            stroke_opacity=0.8
        )
        self.add(ring3d)
        self.play(
            ring3d.animate.scale(15).set_stroke(opacity=0),
            run_time=0.8
        )
        self.remove(ring3d)
        
        # Final slow rotation
        self.play(
            Rotate(cube_group, PI/8, axis=UP + OUT * 0.3),
            Rotate(energy_dots, PI/4, about_point=ORIGIN, axis=OUT),
            Rotate(energy_dots2, PI/4, about_point=ORIGIN, axis=UP),
            run_time=2
        )
        
        self.wait(1)
        
        # Fade out
        everything = VGroup(cube_group, orbit_ring, orbit_ring2, energy_dots, energy_dots2)
        self.play(
            FadeOut(everything),
            FadeOut(logo),
            run_time=1.5
        )
