import json
import uuid
import base64
from pathlib import Path

from IPython.display import HTML, display

from klotho.utils.playback.supersonic.cdn import (
    SUPERSONIC_CDN,
    SUPERSONIC_CORE_CDN,
    SUPERSONIC_SYNTHDEFS_CDN,
    SUPERSONIC_SAMPLES_CDN,
    SCHEDULER_JS_PATH,
    DRAW_JS_PATH,
)

MANIFEST_PATH = Path(__file__).parent / "assets" / "manifest.json"
SYNTHDEFS_DIR = Path(__file__).parent / "assets" / "synthdefs"
_WIDGET_JS_PATH = Path(__file__).parent / "_engine_widget.js"
_WIDGET_JS_TEMPLATE = None


def _convert_numpy_types(obj):
    try:
        import numpy as np
        if isinstance(obj, dict):
            return {k: _convert_numpy_types(v) for k, v in obj.items()}
        elif isinstance(obj, list):
            return [_convert_numpy_types(v) for v in obj]
        elif isinstance(obj, (np.floating, np.float64, np.float32)):
            return float(obj)
        elif isinstance(obj, (np.integer, np.int64, np.int32)):
            return int(obj)
        else:
            return obj
    except ImportError:
        return obj


def _load_manifest():
    if MANIFEST_PATH.exists():
        return json.loads(MANIFEST_PATH.read_text())
    return {"synths": {}, "inserts": {}}


def _load_synthdef_assets():
    assets = {}
    if SYNTHDEFS_DIR.exists():
        for path in SYNTHDEFS_DIR.glob("*.scsyndef"):
            name = path.stem
            assets[name] = base64.b64encode(path.read_bytes()).decode("ascii")
    return assets


def _load_widget_template():
    global _WIDGET_JS_TEMPLATE
    if _WIDGET_JS_TEMPLATE is None:
        _WIDGET_JS_TEMPLATE = _WIDGET_JS_PATH.read_text()
    return _WIDGET_JS_TEMPLATE


class SuperSonicEngine:
    def __init__(self, events, ring_time=5):
        self.events = _convert_numpy_types(events)
        self.ring_time = ring_time
        self.widget_id = f"klotho_ss_{uuid.uuid4().hex[:8]}"
        self.manifest = _load_manifest()
        self.synthdef_assets = _load_synthdef_assets()

    def _needed_synthdefs(self):
        names = set()
        for ev in self.events:
            if ev.get("type") == "new" and ev.get("synthName"):
                name = ev["synthName"]
                if name != "__rest__":
                    names.add(name)
        return names

    def _generate_html(self):
        events_json = json.dumps(self.events)
        manifest_json = json.dumps(self.manifest)
        synthdef_assets_json = json.dumps(self.synthdef_assets)
        needed = self._needed_synthdefs()

        config_json = json.dumps({
            "baseURL": f"{SUPERSONIC_CDN}/dist/",
            "coreBaseURL": SUPERSONIC_CORE_CDN,
            "synthdefBaseURL": SUPERSONIC_SYNTHDEFS_CDN,
            "sampleBaseURL": SUPERSONIC_SAMPLES_CDN,
        })

        draw_js = DRAW_JS_PATH.read_text() if DRAW_JS_PATH.exists() else ""
        scheduler_js = SCHEDULER_JS_PATH.read_text() if SCHEDULER_JS_PATH.exists() else ""

        needed_json = json.dumps(list(needed))
        wid = self.widget_id

        widget_js = (_load_widget_template()
                     .replace('__WID__', wid)
                     .replace('__EVENTS_JSON__', events_json)
                     .replace('__MANIFEST_JSON__', manifest_json)
                     .replace('__SYNTHDEF_ASSETS_JSON__', synthdef_assets_json)
                     .replace('__NEEDED_JSON__', needed_json)
                     .replace('__SS_CONFIG_JSON__', config_json)
                     .replace('__RING_TIME__', str(self.ring_time)))

        html = f'''
<div id="{wid}" style="
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    background: #1a1a2e;
    border-radius: 6px;
    user-select: none;
">
    <button id="{wid}_toggle" style="
        width: 32px;
        height: 32px;
        border: none;
        border-radius: 4px;
        background: #16213e;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 0;
    ">
        <span id="{wid}_icon" style="
            width: 0;
            height: 0;
            border-top: 7px solid transparent;
            border-bottom: 7px solid transparent;
            border-left: 12px solid #4ade80;
            margin-left: 3px;
        "></span>
    </button>
    <button id="{wid}_loop" style="
        width: 28px;
        height: 28px;
        border: none;
        border-radius: 4px;
        background: #16213e;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 0;
        opacity: 0.5;
    ">
        <svg id="{wid}_loop_svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#a0a0a0" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
            <path d="M17 2l4 4-4 4"></path>
            <path d="M3 11v-1a4 4 0 0 1 4-4h14"></path>
            <path d="M7 22l-4-4 4-4"></path>
            <path d="M21 13v1a4 4 0 0 1-4 4H3"></path>
        </svg>
    </button>
    <span id="{wid}_status" style="
        color: #666;
        font-size: 10px;
        min-width: 60px;
    ">ready</span>
</div>

<script>
{draw_js}

{scheduler_js}

{widget_js}
</script>
'''
        return html

    def display(self):
        html = self._generate_html()
        return display(HTML(html))

    def _repr_html_(self):
        return self._generate_html()
