import asyncio
from dataclasses import dataclass, field
from typing import Callable

from pyscript import document, window

from pyscript.js_modules import three as THREE
from pyscript.js_modules.stats_gl import default as StatsGL
from pyscript.js_modules import lsgeo, line2, linemat

from multipyjs import MICROPYTHON, new, call, to_js, create_proxy

@dataclass
class SoundPlayer:
    sound: THREE.Audio = field()
    on_start: Callable[[], None] = field()
    on_stop: Callable[[], None] = field(default=lambda: None)

    _start_time: float = -1.0

    def play(self):
        self.sound.stop()
        self.on_start()
        self._start_time = self.sound.context.currentTime
        self.sound.play()

    def stop(self):
        self.sound.stop()
        self.on_stop()
        self._start_time = -1.0

    def toggle(self):
        if self.sound.isPlaying:
            self.stop()
        else:
            self.play()

    @property
    def running_time(self):
        if self.sound.isPlaying:
            return self.sound.context.currentTime - self._start_time
        elif self._start_time != -1.0:
            self.stop()
        return 0.0

def get_renderer():
    renderer = new(THREE.WebGLRenderer, antialias=True)
    renderer.setSize(window.innerWidth, window.innerHeight)
    renderer.setPixelRatio(window.devicePixelRatio)
    renderer.setClearColor(0xF5F0DC)
    pyterms = list(document.getElementsByTagName("py-terminal"))
    if pyterms:
        pyterm = pyterms[0]
        pyterm.parentNode.removeChild(pyterm)
        document.getElementById("pyterm").appendChild(pyterm)

    document.getElementById("threejs").appendChild(renderer.domElement)

    initial = {0: "115px", 1: "calc(100vh - 120px)"}
    @create_proxy
    def split_element_style(dimension, size, gutter_size, index):
        if index in initial:
            result = {dimension: initial.pop(index)}
        else:
            result = {dimension: f"calc({int(size)}vh - {gutter_size}px)"}
        return to_js(result)

    call(
        window.Split,
        ["#pyterm", "#threejs"],
        direction="vertical",
        elementStyle=split_element_style,
        minSize=0,
        maxSize=to_js([120, 10000]),
    )
    return renderer

def get_ortho_camera(view_size):
    aspect_ratio = window.innerWidth / window.innerHeight
    camera = new(
        THREE.OrthographicCamera,
        -view_size * aspect_ratio,  # Left
        view_size * aspect_ratio,   # Right
        view_size,                  # Top
        -view_size,                 # Bottom
        -view_size,                 # Near plane
        view_size,                  # Far plane
    )
    camera.updateProjectionMatrix()
    camera.position.set(0, 0, 0)
    return camera

def get_loading_manager():
    loading_mgr = new(THREE.LoadingManager)
    ev = asyncio.Event()

    @create_proxy
    def on_start(url, itemsLoaded, itemsTotal):
        print(f'[{itemsLoaded}/{itemsTotal}] Started loading file: {url}')
    loading_mgr.onStart = on_start

    @create_proxy
    def on_progress(url, itemsLoaded, itemsTotal):
        print(f'[{itemsLoaded}/{itemsTotal}] Loading file: {url}')
    loading_mgr.onProgress = on_progress

    @create_proxy
    def on_error(url):
        print(f'There was a problem loading {url}')
    loading_mgr.onError = on_error

    @create_proxy
    def on_load():
        print('Loading assets complete!')
        ev.set()
    loading_mgr.onLoad = on_load

    return loading_mgr, ev


def get_perspective_camera():
    aspect_ratio = window.innerWidth / window.innerHeight
    camera = new(
        THREE.PerspectiveCamera,
        45,             # fov
        aspect_ratio,
        0.25,           # near plane
        300,            # far plane
    )
    camera.position.set(0, 0, 30)
    return camera

def get_stats_gl(renderer):
    stats = new(StatsGL, trackGPU=True, horizontal=False)
    stats.init(renderer)
    stats.dom.style.removeProperty("left")
    stats.dom.style.right = "90px"
    document.getElementById("stats").appendChild(stats.dom)
    return stats

def bg_from_v(*vertices):
    geometry = new(THREE.BufferGeometry)
    vertices_f32a = new(Float32Array, vertices)  # noqa: F821 Float32Array is defined in js
    attr = new(THREE.Float32BufferAttribute, vertices_f32a, 3)
    return geometry.setAttribute('position', attr)

def bg_from_p(*points):
    buf = new(THREE.BufferGeometry)
    buf.setFromPoints(
        [new(THREE.Vector3, p[0], p[1], p[2]) for p in points]
    )
    return buf

def clear():
    # toggle stats and terminal?
    stats_style = document.getElementById("stats-off").style
    if stats_style.display == "none":
        # turn stuff back on
        stats_style.removeProperty("display")
        document.getElementById("pyterm").style.height = "115px"
        document.getElementById("threejs").style.height = "calc(100vh - 120px)"
        for e in document.getElementsByClassName("gutter"):
            e.style.removeProperty("display")
        for e in document.getElementsByClassName("xterm-helper-textarea"):
            e.focus()
            break
        return

    # no longer focus on xterm
    document.activeElement.blur()
    # hide stats
    document.getElementById("stats-off").style.display = "none"
    # hide pyterm and split gutter
    document.getElementById("pyterm").style.height = "0vh"
    document.getElementById("threejs").style.height = "100vh"
    for e in document.getElementsByClassName("gutter"):
        e.style.display = "none"
    # hide ltk ad
    for e in document.getElementsByClassName("ltk-built-with"):
        e.style.display = "none"
    # hide pyscript ad
    for e in document.getElementsByTagName("div"):
        style = e.getAttribute("style")
        if style and style.startswith("z-index:999"):
            e.style.display = "none"
    for e in document.getElementsByTagName("svg"):
        style = e.getAttribute("style")
        if style and style.startswith("z-index:999"):
            e.style.display = "none"
