from manim import *
import numpy as np
import math


class GeneratedScene(Scene):
    def construct(self):
        self.camera.background_color = "#0E1116"

        TEXT_C = "#E6EAF0"
        ACCENT = "#E8A33D"
        CORAL = "#D96B5C"
        NEUTRAL = "#E6EAF0"

        def safe_text(text, max_width=6.0, font_size=28, color=TEXT_C, weight=NORMAL):
            t = Text(text, font_size=font_size, color=color, weight=weight)
            if t.width > max_width:
                t.scale(max_width / t.width)
            return t

        # ---- Build the 10x10 grid of dots (the single source of truth) ----
        spacing = 0.5
        dot_radius = 0.13
        all_dots = []
        dots = VGroup()
        for r in range(10):
            for c in range(10):
                pos = np.array([(c - 4.5) * spacing, (4.5 - r) * spacing, 0])
                d = Circle(radius=dot_radius, color=NEUTRAL,
                           fill_opacity=0.6, stroke_width=0)
                d.move_to(pos)
                all_dots.append(d)
                dots.add(d)
        sick_dot = all_dots[0]                       # row 0, col 0
        fp_dots = [all_dots[i] for i in [1, 2, 3, 4, 5]]  # ~5 false positives
        remaining_idx = [i for i in range(100) if i not in [0, 1, 2, 3, 4, 5]]
        remaining = VGroup(*[all_dots[i] for i in remaining_idx])
        right_block = VGroup(*[all_dots[i] for i in range(1, 100)])

        # =================== Scene 1 — Anchor the Population ===================
        self.play(FadeIn(dots), run_time=0.6)
        self.wait(0.3)

        sick_target = Circle(radius=dot_radius * 1.4, color=ACCENT,
                             fill_opacity=1.0, stroke_width=0
                             ).move_to(sick_dot.get_center())
        self.play(Transform(sick_dot, sick_target), run_time=0.8)
        glow = Circle(radius=dot_radius * 2.4, color=ACCENT,
                      fill_opacity=0.22, stroke_width=0).move_to(sick_dot.get_center())
        self.play(FadeIn(glow), run_time=0.4)
        self.play(FadeOut(glow), run_time=0.4)

        cap1 = safe_text("100 people", max_width=4.0, font_size=30)
        cap1.to_corner(UL, buff=0.35)
        cap2 = safe_text("1% have the disease", max_width=4.0, font_size=26, color=ACCENT)
        cap2.next_to(cap1, DOWN, aligned_edge=LEFT, buff=0.15)
        self.play(Write(cap1), run_time=0.5)
        self.play(FadeIn(cap2, shift=UP * 0.15), run_time=0.5)

        bracket = Brace(sick_dot, LEFT, color=ACCENT)
        blabel = safe_text("1 sick", max_width=1.6, font_size=22, color=ACCENT)
        blabel.next_to(bracket, LEFT, buff=0.15)
        self.play(GrowFromCenter(bracket), FadeIn(blabel, shift=RIGHT * 0.15), run_time=1.0)
        self.wait(1.2)

        divider = Line([-2.0, -2.6, 0], [-2.0, 2.6, 0],
                       color=TEXT_C, stroke_width=1.5, stroke_opacity=0.5)
        self.play(FadeOut(bracket), FadeOut(blabel), Create(divider), run_time=0.8)
        self.play(FadeOut(cap1), FadeOut(cap2), run_time=0.4)

        # =================== Scene 2 — Sensitivity on the Sick ===================
        s2_l1 = safe_text("Test catches 90%", max_width=4.0, font_size=28, color=ACCENT)
        s2_l2 = safe_text("of the sick", max_width=4.0, font_size=24)
        s2_l1.to_corner(UL, buff=0.35)
        s2_l2.next_to(s2_l1, DOWN, aligned_edge=LEFT, buff=0.15)

        self.play(right_block.animate.set_fill(opacity=0.35), run_time=0.6)
        self.play(Write(s2_l1), run_time=0.5)
        self.play(FadeIn(s2_l2, shift=UP * 0.15), run_time=0.5)

        slabel = safe_text("Sick = 1", max_width=1.8, font_size=22, color=ACCENT)
        slabel.next_to(sick_dot, LEFT, buff=0.3)
        self.play(FadeIn(slabel), run_time=0.4)

        meter_w = 1.4
        meter_bg = Rectangle(width=meter_w, height=0.12, color=TEXT_C,
                              fill_opacity=0, stroke_width=1.2, stroke_opacity=0.4)
        meter_bg.next_to(sick_dot, DOWN, buff=0.25).align_to(sick_dot, LEFT)
        meter_fill = Rectangle(width=0.001, height=0.12, color=ACCENT,
                               fill_opacity=0.9, stroke_width=0)
        meter_fill.align_to(meter_bg, LEFT).align_to(meter_bg, DOWN)
        meter_target = Rectangle(width=meter_w * 0.9, height=0.12, color=ACCENT,
                                  fill_opacity=0.9, stroke_width=0)
        meter_target.align_to(meter_bg, LEFT).align_to(meter_bg, DOWN)
        self.play(FadeIn(meter_bg), run_time=0.3)
        self.play(Transform(meter_fill, meter_target), run_time=1.2)

        tp_label = safe_text("True positive = 0.9", max_width=3.2, font_size=22, color=ACCENT)
        tp_label.next_to(meter_bg, DOWN, buff=0.18)
        self.play(FadeIn(tp_label, shift=UP * 0.15), run_time=0.6)
        self.wait(1.2)

        self.play(right_block.animate.set_fill(opacity=0.6), run_time=0.6)
        self.play(FadeOut(slabel), FadeOut(meter_bg), FadeOut(meter_fill),
                  FadeOut(tp_label), FadeOut(divider),
                  FadeOut(s2_l1), FadeOut(s2_l2), run_time=0.5)

        # =================== Scene 3 — The Base-Rate Trap ===================
        s3_l1 = safe_text("5% of healthy test positive", max_width=5.0, font_size=26, color=CORAL)
        s3_l2 = safe_text("≈ 4.95 people", max_width=3.5, font_size=24, color=CORAL)
        s3_l1.to_corner(UL, buff=0.35)
        s3_l2.next_to(s3_l1, DOWN, aligned_edge=LEFT, buff=0.15)
        self.play(Write(s3_l1), run_time=0.5)
        self.play(FadeIn(s3_l2, shift=UP * 0.15), run_time=0.5)

        h_meter_w = 4.0
        h_meter_bg = Rectangle(width=h_meter_w, height=0.12, color=TEXT_C,
                                fill_opacity=0, stroke_width=1.2, stroke_opacity=0.4)
        h_meter_bg.next_to(dots, DOWN, buff=0.35)
        h_meter_fill = Rectangle(width=0.001, height=0.12, color=CORAL,
                                 fill_opacity=0.9, stroke_width=0)
        h_meter_fill.align_to(h_meter_bg, LEFT).align_to(h_meter_bg, DOWN)
        h_meter_target = Rectangle(width=h_meter_w * 0.05, height=0.12, color=CORAL,
                                   fill_opacity=0.9, stroke_width=0)
        h_meter_target.align_to(h_meter_bg, LEFT).align_to(h_meter_bg, DOWN)
        self.play(FadeIn(h_meter_bg), run_time=0.3)
        self.play(Transform(h_meter_fill, h_meter_target), run_time=1.0)

        for d in fp_dots:
            dt = Circle(radius=dot_radius, color=CORAL,
                        fill_opacity=0.9, stroke_width=0).move_to(d.get_center())
            self.play(Transform(d, dt), run_time=0.3)

        fp_label = safe_text("False positives ≈ 4.95", max_width=4.0, font_size=22, color=CORAL)
        fp_label.next_to(h_meter_bg, DOWN, buff=0.18)
        self.play(FadeIn(fp_label, shift=UP * 0.15), run_time=0.6)
        self.wait(1.5)

        self.play(FadeOut(h_meter_bg), FadeOut(h_meter_fill), FadeOut(fp_label),
                  FadeOut(s3_l1), FadeOut(s3_l2), run_time=0.5)

        # =================== Scene 4 — Side-by-Side Comparison ===================
        s4_l1 = safe_text("Everyone who tested positive", max_width=5.5, font_size=26)
        s4_l2 = safe_text("≈ 5.85 people", max_width=3.5, font_size=24)
        s4_l1.to_corner(UL, buff=0.35)
        s4_l2.next_to(s4_l1, DOWN, aligned_edge=LEFT, buff=0.15)
        self.play(Write(s4_l1), run_time=0.5)
        self.play(FadeIn(s4_l2, shift=UP * 0.15), run_time=0.5)

        row_y = 0.0
        row_spacing = 0.42
        n = 6
        total_w = (n - 1) * row_spacing
        start_x = -total_w / 2
        row_positions = [np.array([start_x + i * row_spacing, row_y, 0]) for i in range(n)]

        accent_row = Circle(radius=dot_radius * 0.7, color=ACCENT,
                             fill_opacity=1.0, stroke_width=0).move_to(row_positions[0])
        coral_targets = []
        for i, d in enumerate(fp_dots):
            t = Circle(radius=dot_radius, color=CORAL,
                       fill_opacity=0.9, stroke_width=0).move_to(row_positions[i + 1])
            coral_targets.append(t)

        self.play(
            Transform(sick_dot, accent_row),
            *[Transform(d, t) for d, t in zip(fp_dots, coral_targets)],
            run_time=1.2
        )
        self.play(remaining.animate.set_fill(opacity=0.15), run_time=0.6)

        row_grp = VGroup(sick_dot, *fp_dots)
        total_label = safe_text("Positive = 5.85", max_width=3.0, font_size=22, color=TEXT_C)
        total_label.next_to(row_grp, RIGHT, buff=0.4)
        self.play(FadeIn(total_label, shift=LEFT * 0.15), run_time=0.5)

        brace = Brace(row_grp, DOWN, color=TEXT_C)
        brace_label = safe_text("all positives", max_width=2.2, font_size=20, color=TEXT_C)
        brace_label.next_to(brace, DOWN, buff=0.1)
        self.play(GrowFromCenter(brace), FadeIn(brace_label), run_time=0.6)
        self.wait(1.2)

        vdiv_x = (row_positions[0][0] + row_positions[1][0]) / 2
        vdiv = Line([vdiv_x, -0.45, 0], [vdiv_x, 0.45, 0],
                    color=TEXT_C, stroke_width=1.5, stroke_opacity=0.6)
        self.play(Create(vdiv), run_time=0.4)
        self.wait(0.6)

        self.play(FadeOut(brace), FadeOut(brace_label), FadeOut(total_label),
                  FadeOut(vdiv), FadeOut(s4_l1), FadeOut(s4_l2), run_time=0.5)

        # =================== Scene 5 — Convert to Probability ===================
        eq_lhs = MathTex("P(\\text{disease} \\mid \\text{positive}) =",
                         font_size=34, color=TEXT_C)

        # Build fraction manually to color numerator/denominator independently
        num_tex = MathTex("0.9", font_size=34, color=ACCENT)
        den_tex = MathTex("5.85", font_size=34, color=CORAL)
        frac_line = Line(LEFT * 0.35, RIGHT * 0.35, color=TEXT_C, stroke_width=1.5)
        frac = VGroup(num_tex, frac_line, den_tex).arrange(DOWN, buff=0.06)

        eq = VGroup(eq_lhs, frac).arrange(RIGHT, buff=0.25)
        eq.move_to([0, 2.7, 0])
        self.play(Write(eq_lhs), run_time=0.8)
        self.play(FadeIn(frac, shift=LEFT * 0.2), run_time=0.6)

        self.play(sick_dot.animate.scale(1.4), run_time=0.3)
        self.play(sick_dot.animate.scale(1 / 1.4), run_time=0.3)
        self.play(VGroup(*fp_dots).animate.scale(1.15), run_time=0.3)
        self.play(VGroup(*fp_dots).animate.scale(1 / 1.15), run_time=0.3)

        decimal = MathTex("0.154", font_size=40, color=TEXT_C).move_to(frac.get_center())
        self.play(Transform(frac, decimal), run_time=0.6)

        result = MathTex("\\approx 15\\%", font_size=40, color=ACCENT).move_to(frac.get_center())
        self.play(Transform(frac, result), run_time=0.5)
        self.play(frac.animate.scale(1.15), run_time=0.2)
        self.play(frac.animate.scale(1 / 1.15), run_time=0.2)
        self.wait(1.2)

        eq_grp = VGroup(eq_lhs, frac)
        self.play(eq_grp.animate.scale(0.6).to_edge(UR, buff=0.4), run_time=0.6)

        # =================== Scene 6 — The Insight ===================
        line1 = safe_text("Most positives are healthy", max_width=5.5, font_size=30, color=TEXT_C)
        line2 = safe_text("because healthy people outnumber the sick",
                          max_width=6.2, font_size=24, color=TEXT_C)
        line1.move_to([0, -1.7, 0])
        line2.move_to([0, -2.4, 0])

        self.play(VGroup(*fp_dots).animate.set_fill(opacity=0.55), run_time=1.0)
        self.play(Write(line1), run_time=0.5)
        self.wait(1.2)
        self.play(Write(line2), run_time=0.5)
        self.wait(1.3)

        self.play(FadeOut(line1), FadeOut(line2), FadeOut(eq_grp), run_time=0.5)

        # =================== Scene 7 — Recombine Through Bayes' Eyes ===================
        self.play(remaining.animate.set_fill(opacity=0.6), run_time=0.6)

        sick_home = np.array([(0 - 4.5) * spacing, (4.5 - 0) * spacing, 0])
        sick_back = Circle(radius=dot_radius * 1.4, color=ACCENT,
                           fill_opacity=1.0, stroke_width=0).move_to(sick_home)
        coral_back = []
        for i, d in enumerate(fp_dots):
            idx = i + 1
            r, c = idx // 10, idx % 10
            pos = np.array([(c - 4.5) * spacing, (4.5 - r) * spacing, 0])
            t = Circle(radius=dot_radius, color=CORAL,
                       fill_opacity=0.9, stroke_width=0).move_to(pos)
            coral_back.append(t)
        self.play(
            Transform(sick_dot, sick_back),
            *[Transform(d, t) for d, t in zip(fp_dots, coral_back)],
            run_time=0.8
        )

        sick_bracket = Brace(sick_dot, LEFT, color=ACCENT)
        sick_blabel = safe_text("true positive", max_width=2.2, font_size=18, color=ACCENT)
        sick_blabel.next_to(sick_bracket, LEFT, buff=0.12)
        coral_grp = VGroup(*fp_dots)
        coral_bracket = Brace(coral_grp, UP, color=CORAL)
        coral_blabel = safe_text("false positives", max_width=2.4, font_size=18, color=CORAL)
        coral_blabel.next_to(coral_bracket, UP, buff=0.1)
        self.play(GrowFromCenter(sick_bracket), FadeIn(sick_blabel),
                  GrowFromCenter(coral_bracket), FadeIn(coral_blabel), run_time=0.8)

        final_cap = safe_text("Base rates matter most", max_width=5.0, font_size=32, color=TEXT_C)
        final_cap.to_edge(DOWN, buff=0.5)
        self.play(Write(final_cap), run_time=0.6)
        self.wait(1.5)

        self.play(FadeOut(dots), FadeOut(sick_bracket), FadeOut(sick_blabel),
                  FadeOut(coral_bracket), FadeOut(coral_blabel), run_time=0.8)
        self.play(FadeOut(final_cap), run_time=0.6)
        self.wait(0.3)