"""
طراحی شده توسط محمد نورائی
تمامی حقوق محفوظ است (C) ۱۴۰۵ - تا پایان زمان

Written by Mohammad Nouraei.
CopyRight (C) 2026 - Until end of time
"""
import math
from js import window, document, THREE, cancelAnimationFrame, requestAnimationFrame, katex
from pyodide.ffi import create_proxy, to_js

scene = None
camera = None
renderer = None
controls = None
latheMesh = None
latheWireframe = None
profileLine = None
profilePointsMesh = None
axisLine = None

currentAngle = 0.01
animState = "SWEEP"  
holdCounter = 0
animationFrameId = None
lathePoints = []
anim_proxy = None


def get_val(elem_id, default=3.0):
    try:
        val = float(document.getElementById(elem_id).value)
        return val
    except Exception:
        return default


def render_katex(latex_code, element_id):
    elem = document.getElementById(element_id)
    if elem:
        opts = to_js({"displayMode": True}, dict_converter=window.Object.fromEntries)
        katex.render(latex_code, elem, opts)


def setupThreeJS():
    global scene, camera, renderer, controls, animationFrameId, anim_proxy, axisLine

    container = document.getElementById('threejs-container')
    container.innerHTML = ''

    scene = THREE.Scene.new()
    scene.background = THREE.Color.new(0xf8fafc)

    width = container.clientWidth
    height = container.clientHeight or 380

    camera = THREE.PerspectiveCamera.new(45, width / height, 0.1, 1000)
    camera.position.set(9, 7, 11)

    options = to_js({"antialias": True, "alpha": True}, dict_converter=window.Object.fromEntries)
    renderer = THREE.WebGLRenderer.new(options)
    renderer.setSize(width, height)
    renderer.setPixelRatio(min(window.devicePixelRatio, 2))
    renderer.shadowMap.enabled = True
    container.appendChild(renderer.domElement)

    controls = THREE.OrbitControls.new(camera, renderer.domElement)
    controls.enableDamping = True
    controls.dampingFactor = 0.05

    ambientLight = THREE.AmbientLight.new(0xffffff, 0.7)
    scene.add(ambientLight)

    dirLight1 = THREE.DirectionalLight.new(0xffffff, 0.9)
    dirLight1.position.set(12, 18, 12)
    scene.add(dirLight1)

    dirLight2 = THREE.DirectionalLight.new(0x3b82f6, 0.35)
    dirLight2.position.set(-12, -10, -12)
    scene.add(dirLight2)

    dirLight3 = THREE.DirectionalLight.new(0xf59e0b, 0.25)
    dirLight3.position.set(0, 15, -15)
    scene.add(dirLight3)

    gridHelper = THREE.GridHelper.new(20, 20, 0xcbce3, 0xe2e8f0)
    gridHelper.position.y = -4
    scene.add(gridHelper)

    axisGeom = THREE.BufferGeometry.new()
    axis_pts = to_js([THREE.Vector3.new(0, -6, 0), THREE.Vector3.new(0, 6, 0)])
    axisGeom.setFromPoints(axis_pts)
    axisMat = THREE.LineDashedMaterial.new(to_js({
        "color": 0xef4444,
        "dashSize": 0.3,
        "gapSize": 0.15,
        "linewidth": 2
    }, dict_converter=window.Object.fromEntries))
    axisLine = THREE.Line.new(axisGeom, axisMat)
    axisLine.computeLineDistances()
    scene.add(axisLine)

    if animationFrameId is not None:
        cancelAnimationFrame(animationFrameId)

    if anim_proxy is None:
        anim_proxy = create_proxy(animate)

    animate()


def disposeObject(obj):
    if not obj:
        return
    if hasattr(obj, 'geometry') and obj.geometry:
        obj.geometry.dispose()
    if hasattr(obj, 'material') and obj.material:
        if window.Array.isArray(obj.material):
            for m in obj.material:
                m.dispose()
        else:
            obj.material.dispose()
    scene.remove(obj)


def buildLathe(angle):
    global latheMesh, latheWireframe

    if latheMesh:
        disposeObject(latheMesh)
    if latheWireframe:
        disposeObject(latheWireframe)

    if angle < 0.02:
        return

    try:
        opacity = float(document.getElementById('opacity-input').value)
    except Exception:
        opacity = 0.75

    segments = max(8, math.ceil((angle / (math.pi * 2)) * 48))

    js_points = to_js(lathePoints)
    geometry = THREE.LatheGeometry.new(js_points, segments, 0, angle)

    mat_opts = to_js({
        "color": 0x2563eb,
        "side": THREE.DoubleSide,
        "transparent": True,
        "opacity": opacity,
        "shininess": 85,
        "specular": 0x60a5fa
    }, dict_converter=window.Object.fromEntries)
    material = THREE.MeshPhongMaterial.new(mat_opts)

    latheMesh = THREE.Mesh.new(geometry, material)
    scene.add(latheMesh)

    wireframeGeom = THREE.WireframeGeometry.new(geometry)
    wire_opts = to_js({
        "color": 0x1d4ed8,
        "transparent": True,
        "opacity": 0.22
    }, dict_converter=window.Object.fromEntries)
    wireframeMat = THREE.LineBasicMaterial.new(wire_opts)

    latheWireframe = THREE.LineSegments.new(wireframeGeom, wireframeMat)
    scene.add(latheWireframe)


def initOrUpdateThreeJS(*args):
    global lathePoints, profileLine, profilePointsMesh, currentAngle, animState, holdCounter, scene

    if scene is None:
        setupThreeJS()

    if profileLine:
        disposeObject(profileLine)
    if profilePointsMesh:
        disposeObject(profilePointsMesh)

    shape = document.getElementById('shape-select').value
    lathePoints = []

    if shape == 'rectangle':
        r = get_val('input-r', 3.0)
        h = get_val('input-h', 5.0)
        lathePoints.append(THREE.Vector2.new(0, -h/2))
        lathePoints.append(THREE.Vector2.new(r, -h/2))
        lathePoints.append(THREE.Vector2.new(r, h/2))
        lathePoints.append(THREE.Vector2.new(0, h/2))

    elif shape == 'triangle':
        r = get_val('input-r', 3.0)
        h = get_val('input-h', 5.0)
        lathePoints.append(THREE.Vector2.new(0, -h/2))
        lathePoints.append(THREE.Vector2.new(r, -h/2))
        lathePoints.append(THREE.Vector2.new(0, h/2))

    elif shape == 'semicircle':
        R = get_val('input-R', 3.0)
        steps = 32
        for i in range(steps + 1):
            theta = (i / steps) * math.pi
            x = R * math.sin(theta)
            y = R * math.cos(theta)
            lathePoints.append(THREE.Vector2.new(x, y))

    elif shape == 'quartercircle':
        R = get_val('input-R', 3.0)
        steps = 24
        for i in range(steps + 1):
            theta = (i / steps) * (math.pi / 2)
            x = R * math.sin(theta)
            y = R * math.cos(theta) - R/2
            lathePoints.append(THREE.Vector2.new(x, y))
        lathePoints.append(THREE.Vector2.new(0, -R/2))

    elif shape == 'torus_circle':
        r = get_val('input-r', 1.2)
        R = get_val('input-R', 3.2)
        if r >= R:
            r = R - 0.3
        steps = 32
        for i in range(steps + 1):
            theta = (i / steps) * 2 * math.pi
            x = R + r * math.cos(theta)
            y = r * math.sin(theta)
            lathePoints.append(THREE.Vector2.new(x, y))

    profile_vec3s = [THREE.Vector3.new(p.x, p.y, 0) for p in lathePoints]
    profileGeom = THREE.BufferGeometry.new().setFromPoints(to_js(profile_vec3s))

    prof_mat_opts = to_js({
        "color": 0xec4899,
        "linewidth": 3
    }, dict_converter=window.Object.fromEntries)
    profileMat = THREE.LineBasicMaterial.new(prof_mat_opts)

    profileLine = THREE.LineLoop.new(profileGeom, profileMat)
    scene.add(profileLine)

    ptsGeom = THREE.BufferGeometry.new().setFromPoints(to_js(profile_vec3s))
    ptsMat = THREE.PointsMaterial.new(to_js({
        "color": 0xf59e0b,
        "size": 0.28
    }, dict_converter=window.Object.fromEntries))
    profilePointsMesh = THREE.Points.new(ptsGeom, ptsMat)
    scene.add(profilePointsMesh)

    currentAngle = 0.01
    animState = "SWEEP"
    holdCounter = 0
    buildLathe(currentAngle)


def animate(timestamp=None):
    global animationFrameId, currentAngle, animState, holdCounter

    try:
        speed = float(document.getElementById('speed-input').value)
    except Exception:
        speed = 1.2

    if speed > 0 and not math.isnan(speed):
        step = 0.02 * speed

        if animState == "SWEEP":
            currentAngle += step
            if currentAngle >= math.pi * 2:
                currentAngle = math.pi * 2
                animState = "HOLD"
                holdCounter = 0
            buildLathe(currentAngle)
            if profileLine:
                profileLine.rotation.y = currentAngle
            if profilePointsMesh:
                profilePointsMesh.rotation.y = currentAngle

        elif animState == "HOLD":
            holdCounter += 1
            rotStep = 0.008 * speed
            if latheMesh:
                latheMesh.rotation.y += rotStep
            if latheWireframe:
                latheWireframe.rotation.y += rotStep
            if profileLine:
                profileLine.rotation.y += rotStep
            if profilePointsMesh:
                profilePointsMesh.rotation.y += rotStep

            if holdCounter > 160:
                animState = "RESET"

        elif animState == "RESET":
            currentAngle = 0.01
            if latheMesh:
                latheMesh.rotation.y = 0
            if latheWireframe:
                latheWireframe.rotation.y = 0
            if profileLine:
                profileLine.rotation.y = 0
            if profilePointsMesh:
                profilePointsMesh.rotation.y = 0
            animState = "SWEEP"
            buildLathe(currentAngle)

    else:
        if currentAngle != math.pi * 2:
            currentAngle = math.pi * 2
            buildLathe(currentAngle)
            if profileLine:
                profileLine.rotation.y = 0
            if profilePointsMesh:
                profilePointsMesh.rotation.y = 0

    if controls:
        controls.update()
    if renderer and scene and camera:
        renderer.render(scene, camera)

    animationFrameId = requestAnimationFrame(anim_proxy)


def updateInputFields(*args):
    shape = document.getElementById('shape-select').value
    container = document.getElementById('inputs-container')
    container.innerHTML = ''

    if shape in ['rectangle', 'triangle']:
        container.innerHTML = '''
            <div style="flex: 1; min-width: 120px;">
                <label class="math-char" style="font-size:13px; font-weight:bold; margin-bottom:4px; display:block;">شعاع قاعده (r):</label>
                <input type="number" id="input-r" value="3" min="0.5" max="8" step="0.5" style="width: 100%;">
            </div>
            <div style="flex: 1; min-width: 120px;">
                <label class="math-char" style="font-size:13px; font-weight:bold; margin-bottom:4px; display:block;">ارتفاع شکل (h):</label>
                <input type="number" id="input-h" value="5" min="0.5" max="8" step="0.5" style="width: 100%;">
            </div>
        '''
    elif shape in ['semicircle', 'quartercircle']:
        container.innerHTML = '''
            <div style="flex: 1; min-width: 120px;">
                <label class="math-char" style="font-size:13px; font-weight:bold; margin-bottom:4px; display:block;">شعاع دایره (R):</label>
                <input type="number" id="input-R" value="3" min="0.5" max="8" step="0.5" style="width: 100%;">
            </div>
        '''
    elif shape == 'torus_circle':
        container.innerHTML = '''
            <div style="flex: 1; min-width: 120px;">
                <label class="math-char" style="font-size:13px; font-weight:bold; margin-bottom:4px; display:block;">شعاع لوله (r):</label>
                <input type="number" id="input-r" value="1.2" min="0.3" max="4" step="0.1" style="width: 100%;">
            </div>
            <div style="flex: 1; min-width: 120px;">
                <label class="math-char" style="font-size:13px; font-weight:bold; margin-bottom:4px; display:block;">فاصله تا محور (R):</label>
                <input type="number" id="input-R" value="3.2" min="1" max="8" step="0.2" style="width: 100%;">
            </div>
        '''

    inputs = document.querySelectorAll('#inputs-container input')
    inp_proxy = create_proxy(lambda e: calculateAndRender())
    for inp in inputs:
        inp.addEventListener('input', inp_proxy)

    calculateAndRender()


def calculateAndRender(*args):
    shape = document.getElementById('shape-select').value
    tabV = document.getElementById('tab-v')
    tabA = document.getElementById('tab-a')

    volumeVal, areaVal, summaryText = 0.0, 0.0, ""

    if shape == 'rectangle':
        r = get_val('input-r', 3.0)
        h = get_val('input-h', 5.0)

        volumeVal = math.pi * r * r * h
        lateralArea = 2 * math.pi * r * h
        baseArea = math.pi * r * r
        areaVal = lateralArea + (2 * baseArea)

        summaryText = f"حجم استوانه: {volumeVal:.2f} | مساحت کل: {areaVal:.2f}"

        tabV.innerHTML = '''
            <p>دوران یک مستطیل حول یکی از اضلاعش، یک <strong>استوانه قائم</strong> پدید می‌آورد.</p>
            <div class="math-display" id="f-v-1"></div>
            <p>مراحل محاسبات با مقادیر داده شده:</p>
            <div class="math-display" id="s-v-1"></div>
        '''
        tabA.innerHTML = '''
            <p>مساحت کل استوانه شامل مساحت جانبی به‌علاوه مساحت دو قاعده دایره‌ای شکل است:</p>
            <div class="math-display" id="f-a-1"></div>
            <p>مراحل محاسبات با مقادیر داده شده:</p>
            <div class="math-display" id="s-a-1"></div>
        '''

        render_katex("V = \\pi r^2 h", "f-v-1")
        render_katex(f"V = \\pi \\times ({r})^2 \\times {h} = {r*r*h}\\pi \\approx {volumeVal:.3f}", "s-v-1")

        render_katex("A_{\\text{total}} = 2\\pi r h + 2\\pi r^2 = 2\\pi r (h + r)", "f-a-1")
        render_katex(f"A_{{\\text{{total}}}} = 2\\pi \\times {r} \\times ({h} + {r}) = {2*r*(h+r)}\\pi \\approx {areaVal:.3f}", "s-a-1")

    elif shape == 'triangle':
        r = get_val('input-r', 3.0)
        h = get_val('input-h', 5.0)

        s = math.sqrt(r * r + h * h)
        volumeVal = (1/3) * math.pi * r * r * h
        lateralArea = math.pi * r * s
        baseArea = math.pi * r * r
        areaVal = lateralArea + baseArea

        summaryText = f"حجم مخروط: {volumeVal:.2f} | مساحت کل: {areaVal:.2f}"

        tabV.innerHTML = '''
            <p>دوران یک مثلث قائم‌الزاویه حول یکی از اضلاع قائمش، یک <strong>مخروط قائم</strong> پدید می‌آورد.</p>
            <div class="math-display" id="f-v-2"></div>
            <p>مراحل محاسبات با مقادیر داده شده:</p>
            <div class="math-display" id="s-v-2"></div>
        '''
        tabA.innerHTML = '''
            <p>مساحت کل مخروط شامل مساحت قاعده و مساحت جانبی است. ابتدا خط مولد ($s$) را محاسبه می‌کنیم:</p>
            <div class="math-display" id="f-a-2"></div>
            <p>مراحل محاسبات مولد و مساحت کل با مقادیر داده شده:</p>
            <div class="math-display" id="s-a-2"></div>
        '''

        render_katex("V = \\frac{1}{3} \\pi r^2 h", "f-v-2")
        render_katex(f"V = \\frac{{1}}{{3}} \\pi \\times ({r})^2 \\times {h} = \\frac{{{r*r*h}}}{{3}}\\pi \\approx {volumeVal:.3f}", "s-v-2")

        render_katex("s = \\sqrt{r^2 + h^2} \\quad , \\quad A_{\\text{total}} = \\pi r s + \\pi r^2 = \\pi r (s + r)", "f-a-2")
        render_katex(f"s = \\sqrt{{{r}^2 + {h}^2}} = \\sqrt{{{r*r + h*h}}} \\approx {s:.3f} \\\\ A_{{\\text{{total}}}} = \\pi \\times {r} \\times ({s:.3f} + {r}) \\approx {areaVal:.3f}", "s-a-2")

    elif shape == 'semicircle':
        R = get_val('input-R', 3.0)

        volumeVal = (4/3) * math.pi * math.pow(R, 3)
        areaVal = 4 * math.pi * R * R

        summaryText = f"حجم کره: {volumeVal:.2f} | مساحت کل: {areaVal:.2f}"

        tabV.innerHTML = '''
            <p>دوران یک نیم‌دایره حول قطر بزرگش، یک <strong>کره سه‌بعدی</strong> ایجاد می‌کند.</p>
            <div class="math-display" id="f-v-4"></div>
            <p>مراحل محاسبات با مقادیر داده شده:</p>
            <div class="math-display" id="s-v-4"></div>
        '''
        tabA.innerHTML = '''
            <p>فرمول محاسبه مساحت کل بیرونی پوسته کره:</p>
            <div class="math-display" id="f-a-4"></div>
            <p>مراحل محاسبات با مقادیر داده شده:</p>
            <div class="math-display" id="s-a-4"></div>
        '''

        render_katex("V = \\frac{4}{3} \\pi R^3", "f-v-4")
        render_katex(f"V = \\frac{{4}}{{3}} \\pi \\times ({R})^3 = \\frac{{{(4*math.pow(R,3)):.2f}}}{{3}}\\pi \\approx {volumeVal:.3f}", "s-v-4")

        render_katex("A = 4 \\pi R^2", "f-a-4")
        render_katex(f"A = 4 \\pi \\times ({R})^2 = {4*R*R}\\pi \\approx {areaVal:.3f}", "s-a-4")

    elif shape == 'quartercircle':
        R = get_val('input-R', 3.0)

        volumeVal = (2/3) * math.pi * math.pow(R, 3)
        areaVal = 3 * math.pi * R * R

        summaryText = f"حجم نیم‌کره: {volumeVal:.2f} | مساحت کل: {areaVal:.2f}"

        tabV.innerHTML = '''
            <p>دوران یک ربع دایره حول یکی از شعاع‌های مرزی آن، یک <strong>نیم‌کره سه‌بعدی</strong> ایجاد می‌کند.</p>
            <div class="math-display" id="f-v-5"></div>
            <p>مراحل محاسبات با مقادیر داده شده:</p>
            <div class="math-display" id="s-v-5"></div>
        '''
        tabA.innerHTML = '''
            <p>مساحت کل نیم‌کره برابر با مساحت پوسته کروی شکل و مساحت قاعده تخت دایره‌ای آن است:</p>
            <div class="math-display" id="f-a-5"></div>
            <p>مراحل محاسبات با مقادیر داده شده:</p>
            <div class="math-display" id="s-a-5"></div>
        '''

        render_katex("V = \\frac{2}{3} \\pi R^3", "f-v-5")
        render_katex(f"V = \\frac{{2}}{{3}} \\pi \\times ({R})^3 = \\frac{{{(2*math.pow(R,3)):.2f}}}{{3}}\\pi \\approx {volumeVal:.3f}", "s-v-5")

        render_katex("A_{\\text{total}} = 2\\pi R^2 + \\pi R^2 = 3\\pi R^2", "f-a-5")
        render_katex(f"A_{{\\text{{total}}}} = 3\\pi \\times ({R})^2 = {3*R*R}\\pi \\approx {areaVal:.3f}", "s-a-5")

    elif shape == 'torus_circle':
        r = get_val('input-r', 1.2)
        R = get_val('input-R', 3.2)
        if r >= R:
            r = R - 0.3

        volumeVal = 2 * (math.pi ** 2) * R * (r ** 2)
        areaVal = 4 * (math.pi ** 2) * R * r

        summaryText = f"حجم طوقه (تورس): {volumeVal:.2f} | مساحت پوسته: {areaVal:.2f}"

        tabV.innerHTML = '''
            <p>دوران یک دایره کامل حول محوری خارج از آن، یک <strong>طوقه سه‌بعدی (Torus / دونات)</strong> ایجاد می‌کند.</p>
            <div class="math-display" id="f-v-7"></div>
            <p>مراحل محاسبات با مقادیر داده شده:</p>
            <div class="math-display" id="s-v-7"></div>
        '''
        tabA.innerHTML = '''
            <p>مساحت پوسته بیرونی طوقه بر اساس قضیه پاپوس محاسبه می‌شود:</p>
            <div class="math-display" id="f-a-7"></div>
            <p>مراحل محاسبات با مقادیر داده شده:</p>
            <div class="math-display" id="s-a-7"></div>
        '''

        render_katex("V = 2 \\pi^2 R r^2", "f-v-7")
        render_katex(f"V = 2 \\pi^2 \\times {R} \\times ({r:.2f})^2 = {2*R*r*r:.2f}\\pi^2 \\approx {volumeVal:.3f}", "s-v-7")

        render_katex("A = 4 \\pi^2 R r", "f-a-7")
        render_katex(f"A = 4 \\pi^2 \\times {R} \\times {r:.2f} = {4*R*r:.2f}\\pi^2 \\approx {areaVal:.3f}", "s-a-7")

    document.getElementById('banner-result').innerText = summaryText
    initOrUpdateThreeJS()


def setup_tab_events():
    buttons = document.querySelectorAll('.tab-btn')
    btn_proxy = create_proxy(on_tab_click)
    for btn in buttons:
        btn.addEventListener('click', btn_proxy)


def on_tab_click(event):
    btn = event.currentTarget
    buttons = document.querySelectorAll('.tab-btn')
    for b in buttons:
        b.classList.remove('active')

    views = document.querySelectorAll('.steps-view')
    for v in views:
        v.classList.remove('active')

    btn.classList.add('active')
    target = btn.getAttribute('data-tab')
    document.getElementById(target).classList.add('active')


def on_window_resize(event=None):
    if not camera or not renderer:
        return
    container = document.getElementById('threejs-container')
    width = container.clientWidth
    height = container.clientHeight or 380

    camera.aspect = width / height
    camera.updateProjectionMatrix()
    renderer.setSize(width, height)


resize_proxy = create_proxy(on_window_resize)
window.addEventListener('resize', resize_proxy)

btn_update_proxy = create_proxy(lambda e: initOrUpdateThreeJS())
document.getElementById('btn-update').addEventListener('click', btn_update_proxy)

shape_select_proxy = create_proxy(lambda e: updateInputFields())
document.getElementById('shape-select').addEventListener('change', shape_select_proxy)

opacity_proxy = create_proxy(lambda e: buildLathe(currentAngle))
document.getElementById('opacity-input').addEventListener('input', opacity_proxy)

setup_tab_events()
updateInputFields()


def trigger_resize(timestamp=None):
    window.dispatchEvent(window.Event.new('resize'))


window.setTimeout(create_proxy(trigger_resize), 150)