"""MoviePy showcase — Part 4 (Audio), 5 (Composition), 6 (Drawing), 7 (Output)."""

from __future__ import annotations

from moviepy import (
    AudioArrayClip,
    AudioClip,
    ColorClip,
    CompositeAudioClip,
    CompositeVideoClip,
    ImageClip,
    TextClip,
    VideoClip,
    concatenate_audioclips,
    concatenate_videoclips,
)
from moviepy.audio.fx import (
    AudioDelay,
    AudioFadeIn,
    AudioFadeOut,
    AudioLoop,
    AudioNormalize,
    MultiplyStereoVolume,
    MultiplyVolume,
)
from moviepy.video.compositing.CompositeVideoClip import clips_array
from moviepy.video.tools.drawing import (
    circle,
    color_gradient,
    color_split,
)
import numpy as np

from python_pkg.moviepy_showcase.moviepy_showcase import (
    CLIP_DUR,
    FONT_B,
    FONT_R,
    H,
    W,
    _base_clip,
    _resize_to_canvas,
    _section_header,
    _titled,
)


def _make_sine(freq: float = 440.0, dur: float = CLIP_DUR) -> AudioClip:
    """Pure sine-wave AudioClip."""

    def maker(t: np.ndarray) -> np.ndarray:
        t_arr = np.asarray(t)
        wave = 0.3 * np.sin(2 * np.pi * freq * t_arr.flatten())
        stereo = np.column_stack([wave, wave])
        # MoviePy probes with scalar t=0 and uses len(list(frame0))
        # for nchannels. A (1,2) array iterates as 1 row → nchannels=1.
        # Returning shape (2,) for scalar t lets MoviePy detect 2 channels.
        if t_arr.ndim == 0:
            return stereo[0]
        return stereo

    return AudioClip(maker, duration=dur, fps=44100)


def part4_audio() -> list[VideoClip]:
    """Demonstrate audio clips and all 7 audio effects."""
    scenes: list[VideoClip] = [
        _section_header(
            "Part 4: Audio",
            "AudioClip · AudioArrayClip · CompositeAudioClip · 7 Audio Effects",
        ),
    ]
    bg = ColorClip(size=(W, H), color=(20, 30, 50))

    # AudioClip
    a1 = _make_sine(440, CLIP_DUR)
    c1 = bg.with_duration(CLIP_DUR).with_audio(a1)
    scenes.append(_titled(c1, "AudioClip(sine_440Hz)"))

    # AudioArrayClip
    sr = 44100
    t_arr = np.linspace(0, CLIP_DUR, int(sr * CLIP_DUR), endpoint=False)
    arr = (0.3 * np.sin(2 * np.pi * 880 * t_arr)).astype(np.float64)
    stereo = np.column_stack([arr, arr])
    a2 = AudioArrayClip(stereo, fps=sr)
    c2 = bg.with_duration(CLIP_DUR).with_audio(a2)
    scenes.append(_titled(c2, "AudioArrayClip(numpy_array, fps=44100)  # 880Hz"))

    # CompositeAudioClip
    low = _make_sine(220, CLIP_DUR)
    high = _make_sine(660, CLIP_DUR)
    comp_audio = CompositeAudioClip([low, high])
    c3 = bg.with_duration(CLIP_DUR).with_audio(comp_audio)
    scenes.append(_titled(c3, "CompositeAudioClip([220Hz, 660Hz])"))

    # concatenate_audioclips
    a_cat = concatenate_audioclips([_make_sine(330, 1.0), _make_sine(550, 1.0)])
    c4 = bg.with_duration(CLIP_DUR).with_audio(a_cat)
    scenes.append(_titled(c4, "concatenate_audioclips([330Hz, 550Hz])"))

    # AudioFadeIn
    a_fi = _make_sine(440, CLIP_DUR).with_effects([AudioFadeIn(duration=1.5)])
    c5 = bg.with_duration(CLIP_DUR).with_audio(a_fi)
    scenes.append(_titled(c5, "AudioFadeIn(duration=1.5)"))

    # AudioFadeOut
    a_fo = _make_sine(440, CLIP_DUR).with_effects([AudioFadeOut(duration=1.5)])
    c6 = bg.with_duration(CLIP_DUR).with_audio(a_fo)
    scenes.append(_titled(c6, "AudioFadeOut(duration=1.5)"))

    # AudioDelay
    a_delay = _make_sine(440, CLIP_DUR).with_effects(
        [AudioDelay(offset=0.2, n_repeats=4, decay=1)]
    )
    c7 = bg.with_duration(a_delay.duration).with_audio(a_delay)
    scenes.append(
        _titled(
            c7.with_duration(CLIP_DUR), "AudioDelay(offset=0.2, n_repeats=4, decay=1)"
        )
    )

    # AudioLoop
    short_a = _make_sine(440, 0.5)
    a_loop = short_a.with_effects([AudioLoop(duration=CLIP_DUR)])
    c8 = bg.with_duration(CLIP_DUR).with_audio(a_loop)
    scenes.append(_titled(c8, "AudioLoop(duration=2.0)"))

    # AudioNormalize
    quiet = _make_sine(440, CLIP_DUR)  # already normalized but demonstrates the call
    a_norm = quiet.with_effects([AudioNormalize()])
    c9 = bg.with_duration(CLIP_DUR).with_audio(a_norm)
    scenes.append(_titled(c9, "AudioNormalize()"))

    # MultiplyStereoVolume
    a_stereo = _make_sine(440, CLIP_DUR).with_effects(
        [MultiplyStereoVolume(left=1.0, right=0.2)]
    )
    c10 = bg.with_duration(CLIP_DUR).with_audio(a_stereo)
    scenes.append(_titled(c10, "MultiplyStereoVolume(left=1.0, right=0.2)"))

    # MultiplyVolume
    a_vol = _make_sine(440, CLIP_DUR).with_effects([MultiplyVolume(factor=0.3)])
    c11 = bg.with_duration(CLIP_DUR).with_audio(a_vol)
    scenes.append(_titled(c11, "MultiplyVolume(factor=0.3)"))

    return scenes


def part5_composition() -> list[VideoClip]:
    """Demonstrate composition & concatenation."""
    scenes: list[VideoClip] = [
        _section_header(
            "Part 5: Composition",
            "CompositeVideoClip · concatenate_videoclips · clips_array",
        ),
    ]

    # CompositeVideoClip with bg_color, use_bgclip
    bg = _base_clip()
    overlay = (
        ColorClip(size=(400, 400), color=(255, 50, 50))
        .with_duration(CLIP_DUR)
        .with_position(("center", "center"))
        .with_opacity(0.6)
    )
    comp1 = CompositeVideoClip([bg, overlay], size=(W, H), bg_color=(0, 0, 0))
    scenes.append(_titled(comp1, "CompositeVideoClip(clips, bg_color, use_bgclip)"))

    #  concatenate_videoclips — method='chain'
    c1 = ColorClip(size=(W, H), color=(200, 50, 50)).with_duration(0.7)
    c2 = ColorClip(size=(W, H), color=(50, 200, 50)).with_duration(0.7)
    c3 = ColorClip(size=(W, H), color=(50, 50, 200)).with_duration(0.6)
    cat = concatenate_videoclips([c1, c2, c3], method="chain")
    scenes.append(_titled(cat, "concatenate_videoclips(method='chain')"))

    # concatenate_videoclips — method='compose' with padding
    cat2 = concatenate_videoclips(
        [
            c1.resized((W // 2, H // 2)),
            c2.resized((W // 2, H // 2)),
            c3.resized((W, H)),
        ],
        method="compose",
        bg_color=(0, 0, 0),
        padding=-0.2,
    )
    scenes.append(
        _titled(
            _resize_to_canvas(cat2),
            "concatenate_videoclips(method='compose', padding=-0.2)",
        )
    )

    # concatenate_videoclips with transition
    cat3 = concatenate_videoclips(
        [c1, c2, c3],
        padding=-0.3,
        method="compose",
    )
    scenes.append(
        _titled(
            cat3.with_duration(CLIP_DUR),
            "concatenate_videoclips(padding=-0.3)  # overlap",
        )
    )

    # clips_array
    a = ColorClip(size=(W // 2, H // 2), color=(200, 50, 50)).with_duration(CLIP_DUR)
    b = ColorClip(size=(W // 2, H // 2), color=(50, 200, 50)).with_duration(CLIP_DUR)
    c = ColorClip(size=(W // 2, H // 2), color=(50, 50, 200)).with_duration(CLIP_DUR)
    d = ColorClip(size=(W // 2, H // 2), color=(200, 200, 50)).with_duration(CLIP_DUR)
    grid = clips_array([[a, b], [c, d]])
    scenes.append(_titled(_resize_to_canvas(grid), "clips_array([[a, b], [c, d]])"))

    return scenes


def part6_drawing_tools() -> list[VideoClip]:
    """Demonstrate moviepy.video.tools.drawing functions."""
    scenes: list[VideoClip] = [
        _section_header(
            "Part 6: Drawing Tools", "circle · color_gradient · color_split"
        ),
    ]

    # circle
    circ = circle(
        screensize=(W, H),
        center=(W // 2, H // 2),
        radius=300,
        color=1.0,
        bg_color=0.0,
        blur=30,
    )
    circ_rgb = (np.dstack([circ, circ, circ]) * 255).astype(np.uint8)
    scenes.append(
        _titled(
            ImageClip(circ_rgb, duration=CLIP_DUR),
            "drawing.circle(center, radius=300, blur=30)",
        )
    )

    # color_gradient — linear
    grad = color_gradient(
        size=(W, H),
        p1=(0, 0),
        p2=(W, H),
        color_1=0.0,
        color_2=1.0,
        shape="linear",
    )
    grad_rgb = (np.dstack([grad, grad, grad]) * 255).astype(np.uint8)
    scenes.append(
        _titled(
            ImageClip(grad_rgb, duration=CLIP_DUR),
            "drawing.color_gradient(shape='linear')",
        )
    )

    # color_gradient — radial
    grad_r = color_gradient(
        size=(W, H),
        p1=(W // 2, H // 2),
        radius=500,
        color_1=1.0,
        color_2=0.0,
        shape="radial",
    )
    grad_r_rgb = (np.dstack([grad_r, grad_r, grad_r]) * 255).astype(np.uint8)
    scenes.append(
        _titled(
            ImageClip(grad_r_rgb, duration=CLIP_DUR),
            "drawing.color_gradient(shape='radial', radius=500)",
        )
    )

    # color_split
    split = color_split(
        size=(W, H),
        x=W // 2,
        color_1=0.0,
        color_2=1.0,
        gradient_width=100,
    )
    split_rgb = (np.dstack([split, split, split]) * 255).astype(np.uint8)
    scenes.append(
        _titled(
            ImageClip(split_rgb, duration=CLIP_DUR),
            "drawing.color_split(x=W/2, gradient_width=100)",
        )
    )

    return scenes


def part7_output() -> list[VideoClip]:
    """Label-only slides for output methods + parameters."""
    scenes: list[VideoClip] = [
        _section_header(
            "Part 7: Output Methods",
            "write_videofile · write_gif · save_frame · write_images_sequence",
        ),
    ]

    bg = ColorClip(size=(W, H), color=(15, 20, 35))

    methods = [
        (
            "write_videofile()",
            "filename, fps, codec, bitrate, audio, audio_fps,\n"
            "preset, audio_nbytes, audio_codec, audio_bitrate,\n"
            "audio_bufsize, temp_audiofile, threads,\n"
            "ffmpeg_params, logger, pixel_format",
        ),
        (
            "write_gif()",
            "filename, fps, loop, logger",
        ),
        (
            "save_frame()",
            "filename, t, with_mask",
        ),
        (
            "write_images_sequence()",
            "name_format, fps, with_mask, logger",
        ),
        (
            "write_audiofile()",
            "filename, fps, nbytes, buffersize,\ncodec, bitrate, ffmpeg_params, logger",
        ),
    ]

    for title, params in methods:
        t1 = (
            TextClip(
                text=title, font_size=56, color="cyan", font=FONT_B, margin=(0, 20)
            )
            .with_duration(2.5)
            .with_position(("center", 300))
        )
        t2 = (
            TextClip(
                text=f"Parameters:\n{params}",
                font_size=32,
                color="#dddddd",
                font=FONT_R,
                method="caption",
                size=(W - 300, None),
                text_align="center",
                interline=8,
                margin=(0, 15),
            )
            .with_duration(2.5)
            .with_position(("center", 500))
        )
        scenes.append(CompositeVideoClip([bg.with_duration(2.5), t1, t2], size=(W, H)))

    return scenes
