"""
طراحی شده توسط محمد نورائی
تمامی حقوق محفوظ است (C) ۱۴۰۵ - تا پایان زمان

Written by Mohammad Nouraei.
CopyRight (C) 2026 - Until end of time
"""

import math
from js import window, document, requestAnimationFrame, cancelAnimationFrame
from pyodide.ffi import create_proxy, to_js

maxVal = 10.0
animationFrameId = None
animProgress = 0.0
pulseRadius = 5.0
pulseGrowing = True
current_proxy = None


def to_english_number(val_str):
    if not val_str:
        return ""
    trans_table = str.maketrans({
        '۰': '0', '۱': '1', '۲': '2', '۳': '3', '۴': '4',
        '۵': '5', '۶': '6', '۷': '7', '۸': '8', '۹': '9',
        '٠': '0', '١': '1', '٢': '2', '٣': '3', '٤': '4',
        '٥': '5', '٦': '6', '٧': '7', '٨': '8', '٩': '9',
        '٫': '.', '،': '.',
        '−': '-', '﹣': '-', '－': '-'
    })
    return str(val_str).strip().translate(trans_table)


def parse_input_value(element_id):
    el = document.getElementById(element_id)
    if not el:
        return float('nan')
    clean_val = to_english_number(el.value)
    el.value = clean_val  
    try:
        return float(clean_val)
    except Exception:
        return float('nan')


def switchInputTemplate(eqNum, val1=1, val2=1, val3=0):
    try:
        eqNum = int(eqNum)
    except Exception:
        eqNum = 1

    if not isinstance(val1, (int, float)):
        val1 = 1
    if not isinstance(val2, (int, float)):
        val2 = 1
    if not isinstance(val3, (int, float)):
        val3 = 0

    fmt = document.getElementById(f"eq{eqNum}-format").value
    container = document.getElementById(f"eq{eqNum}-inputs-container")

    if fmt == 'std':
        container.innerHTML = f'''
          <input type="text" inputmode="decimal" id="a{eqNum}" value="{val1}">
          <span class="math-char">x</span>
          <span>+</span>
          <input type="text" inputmode="decimal" id="b{eqNum}" value="{val2}">
          <span class="math-char">y</span>
          <span>=</span>
          <input type="text" inputmode="decimal" id="c{eqNum}" value="{val3}">
        '''
    else:
        container.innerHTML = f'''
          <span class="math-char">y</span>
          <span>=</span>
          <input type="text" inputmode="decimal" id="slope_a{eqNum}" value="{val1}">
          <span class="math-char">x</span>
          <span>+</span>
          <input type="text" inputmode="decimal" id="slope_b{eqNum}" value="{val2}">
        '''


def getStandardCoefficients(eqNum):
    fmt = document.getElementById(f"eq{eqNum}-format").value
    if fmt == 'std':
        a = parse_input_value(f"a{eqNum}")
        b = parse_input_value(f"b{eqNum}")
        c = parse_input_value(f"c{eqNum}")
        return {'a': a, 'b': b, 'c': c}
    else:
        slope_a = parse_input_value(f"slope_a{eqNum}")
        slope_b = parse_input_value(f"slope_b{eqNum}")
        return {'a': -slope_a, 'b': 1.0, 'c': slope_b}


def runEngine(*args):
    eq1 = getStandardCoefficients(1)
    eq2 = getStandardCoefficients(2)

    if (math.isnan(eq1['a']) or math.isnan(eq1['b']) or math.isnan(eq1['c']) or
        math.isnan(eq2['a']) or math.isnan(eq2['b']) or math.isnan(eq2['c'])):
        window.alert("لطفاً همه ضرایب عددی را وارد کنید.")
        return

    if (eq1['a'] == 0 and eq1['b'] == 0) or (eq2['a'] == 0 and eq2['b'] == 0):
        window.alert("در هر معادله حداقل یکی از ضرایب x یا y باید غیر صفر باشد.")
        return

    D = eq1['a'] * eq2['b'] - eq2['a'] * eq1['b']
    solvable = abs(D) > 1e-9
    xSol = 0.0
    ySol = 0.0
    infinite = False

    if solvable:
        xSol = round((eq1['c'] * eq2['b'] - eq2['c'] * eq1['b']) / D, 4)
        ySol = round((eq1['a'] * eq2['c'] - eq2['a'] * eq1['c']) / D, 4)
    else:
        if eq1['a'] != 0:
            infinite = abs(eq1['a'] * eq2['c'] - eq2['a'] * eq1['c']) < 1e-9
        elif eq1['b'] != 0:
            infinite = abs(eq1['b'] * eq2['c'] - eq2['b'] * eq1['c']) < 1e-9
        else:
            infinite = (eq1['c'] == 0 and eq2['c'] == 0)

    header = document.getElementById('finalResultHeader')
    if solvable:
        header.innerHTML = f"پاسخ دستگاه: $x = {xSol}$ , $y = {ySol}$"
    elif infinite:
        header.innerHTML = "دستگاه بی‌شمار جواب دارد (دو خط بر هم منطبق هستند)."
    else:
        header.innerHTML = "دستگاه فاقد جواب است (دو خط موازی هستند)."

    buildEliminationSteps(eq1, eq2, xSol, ySol, solvable, infinite)
    buildSubstitutionSteps(eq1, eq2, xSol, ySol, solvable, infinite)
    buildGraphicalSteps(eq1, eq2, xSol, ySol, solvable, infinite)

    try:
        options = to_js({
            "delimiters": [
                {"left": "$$", "right": "$$", "display": True},
                {"left": "$", "right": "$", "display": False}
            ],
            "throwOnError": False
        }, dict_converter=window.Object.fromEntries)
        window.renderMathInElement(document.body, options)
    except Exception as e:
        print("خطا در رندر KaTeX: ", e)

    animateCanvas(eq1, eq2, xSol, ySol, solvable)


def toScreenX(x, width):
    return (width / 2) + (x * (width / (2 * maxVal)))


def toScreenY(y, height):
    return (height / 2) - (y * (height / (2 * maxVal)))


def drawCartesianGrid(ctx, w, h):
    ctx.fillStyle = '#ffffff'
    ctx.fillRect(0, 0, w, h)

    step = 1
    if maxVal > 15: step = 2
    if maxVal > 30: step = 5
    if maxVal > 70: step = 10
    if maxVal > 150: step = 25

    ctx.strokeStyle = '#f3f4f6'
    ctx.lineWidth = 1
    ctx.textAlign = 'center'
    ctx.textBaseline = 'middle'
    ctx.font = '10px Vazirmatn, Tahoma'
    ctx.fillStyle = '#9ca3af'

    for val in range(-math.floor(maxVal), math.ceil(maxVal) + 1, step):
        if val == 0:
            continue

        sx = toScreenX(val, w)
        ctx.beginPath()
        ctx.moveTo(sx, 0)
        ctx.lineTo(sx, h)
        ctx.stroke()
        ctx.fillText(str(val), sx, toScreenY(0, h) + 12)

        sy = toScreenY(val, h)
        ctx.beginPath()
        ctx.moveTo(0, sy)
        ctx.lineTo(w, sy)
        ctx.stroke()
        ctx.fillText(str(val), toScreenX(0, w) - 14, sy)

    ctx.strokeStyle = '#4b5563'
    ctx.lineWidth = 1.8

    ctx.beginPath()
    ctx.moveTo(0, toScreenY(0, h))
    ctx.lineTo(w, toScreenY(0, h))
    ctx.stroke()

    ctx.beginPath()
    ctx.moveTo(toScreenX(0, w), 0)
    ctx.lineTo(toScreenX(0, w), h)
    ctx.stroke()

    ctx.fillText('0', toScreenX(0, w) - 10, toScreenY(0, h) + 12)


def getEndpoints(a, b, c):
    if abs(b) < 1e-9:
        xVal = c / a
        return {'x1': xVal, 'y1': -maxVal * 1.5, 'x2': xVal, 'y2': maxVal * 1.5}
    x1 = -maxVal * 1.5
    y1 = (c - a * x1) / b
    x2 = maxVal * 1.5
    y2 = (c - a * x2) / b
    return {'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2}


def drawAnimatedLine(ctx, w, h, a, b, c, color, progress):
    pts = getEndpoints(a, b, c)
    if not pts:
        return

    sx1 = toScreenX(pts['x1'], w)
    sy1 = toScreenY(pts['y1'], h)
    sx2 = toScreenX(pts['x2'], w)
    sy2 = toScreenY(pts['y2'], h)

    curX = sx1 + (sx2 - sx1) * progress
    curY = sy1 + (sy2 - sy1) * progress

    ctx.strokeStyle = color
    ctx.lineWidth = 3
    ctx.beginPath()
    ctx.moveTo(sx1, sy1)
    ctx.lineTo(curX, curY)
    ctx.stroke()


def animateCanvas(eq1, eq2, xSol, ySol, solvable):
    global maxVal, animationFrameId, animProgress, pulseRadius, pulseGrowing, current_proxy

    canvas = document.getElementById('graphCanvas')
    ctx = canvas.getContext('2d')

    if animationFrameId is not None:
        cancelAnimationFrame(animationFrameId)

    animProgress = 0.0
    if solvable:
        maxVal = max(10.0, abs(xSol) * 1.8, abs(ySol) * 1.8)
    else:
        maxVal = 10.0

    def drawFrame(timestamp=None):
        global animProgress, pulseRadius, pulseGrowing, animationFrameId

        animProgress += 0.02
        if animProgress > 1.0:
            animProgress = 1.0

        ctx.clearRect(0, 0, canvas.width, canvas.height)
        drawCartesianGrid(ctx, canvas.width, canvas.height)

        drawAnimatedLine(ctx, canvas.width, canvas.height, eq1['a'], eq1['b'], eq1['c'], '#001aff', animProgress)
        drawAnimatedLine(ctx, canvas.width, canvas.height, eq2['a'], eq2['b'], eq2['c'], '#ff0000', animProgress)

        if solvable and animProgress >= 1.0:
            sx = toScreenX(xSol, canvas.width)
            sy = toScreenY(ySol, canvas.height)

            ctx.fillStyle = '#00ff2a'
            ctx.beginPath()
            ctx.arc(sx, sy, 5, 0, 2 * math.pi)
            ctx.fill()

            ctx.strokeStyle = 'rgba(0, 255, 21, 0.45)'
            ctx.lineWidth = 2.5
            ctx.beginPath()
            ctx.arc(sx, sy, pulseRadius, 0, 2 * math.pi)
            ctx.stroke()

            if pulseGrowing:
                pulseRadius += 0.25
                if pulseRadius > 14:
                    pulseGrowing = False
            else:
                pulseRadius -= 0.25
                if pulseRadius < 5:
                    pulseGrowing = True

        animationFrameId = requestAnimationFrame(current_proxy)

    if current_proxy is not None:
        current_proxy.destroy()
    current_proxy = create_proxy(drawFrame)
    drawFrame()


def switchTab(methodId, btnElement):
    contents = document.querySelectorAll('.steps-view')
    for el in contents:
        el.classList.remove('active')

    buttons = document.querySelectorAll('.tab-btn')
    for el in buttons:
        el.classList.remove('active')

    document.getElementById(methodId).classList.add('active')
    if hasattr(btnElement, 'classList'):
        btnElement.classList.add('active')
    else:
        btnElement.target.classList.add('active')


def buildEliminationSteps(eq1, eq2, xSol, ySol, solvable, infinite):
    el = document.getElementById('elimination')
    if not solvable:
        el.innerHTML = getUnsolvableTextHTML(infinite)
        return

    html = "<p>دستگاه اولیه معادلات به صورت زیر ارائه شده است:</p>"
    html += f"$$ \\begin{{cases}} {eq1['a']}x + {eq1['b']}y = {eq1['c']} \\\\ {eq2['a']}x + {eq2['b']}y = {eq2['c']} \\end{{cases}} $$"

    if abs(eq1['b']) < 1e-9:
        html += "<p><strong>گام ۱: تعیین مقدار متغیر مجهول اول</strong><br>"
        html += "در معادله اول ضریب $y$ صفر است؛ بنابراین به صورت مستقیم داریم:</p>"
        html += f"$$ {eq1['a']}x = {eq1['c']} \\implies x = \\frac{{{eq1['c']}}}{{{eq1['a']}}} = {xSol} $$"
        html += "<p><strong>گام ۲: جایگذاری به منظور یافتن مجهول دوم</strong><br>"
        html += "با قرار دادن مقدار به دست آمده برای $x$ در معادله دوم، مجهول $y$ مشخص خواهد شد:</p>"
        html += f"$$ {eq2['a']}({xSol}) + {eq2['b']}y = {eq2['c']} \\implies {eq2['a'] * xSol:.2f} + {eq2['b']}y = {eq2['c']} $$"
        html += f"$$ {eq2['b']}y = {eq2['c'] - eq2['a'] * xSol:.2f} \\implies y = {ySol} $$"
    elif abs(eq2['b']) < 1e-9:
        html += "<p><strong>گام ۱: تعیین مقدار متغیر مجهول اول</strong><br>"
        html += "در معادله دوم ضریب $y$ صفر است؛ پس داریم:</p>"
        html += f"$$ {eq2['a']}x = {eq2['c']} \\implies x = \\frac{{{eq2['c']}}}{{{eq2['a']}}} = {xSol} $$"
        html += "<p><strong>گام ۲: جایگذاری در معادله اول</strong><br>"
        html += "با استفاده از مقدار $x$ در معادله اول، به مجهول $y$ می‌رسیم:</p>"
        html += f"$$ {eq1['a']}({xSol}) + {eq1['b']}y = {eq1['c']} \\implies {eq1['a'] * xSol:.2f} + {eq1['b']}y = {eq1['c']} $$"
        html += f"$$ {eq1['b']}y = {eq1['c'] - eq1['a'] * xSol:.2f} \\implies y = {ySol} $$"
    else:
        html += "<p><strong>گام ۱: حذف کردن متغیر $y$ با همسان‌سازی ضرایب</strong><br>"
        html += f"برای این کار، طرفین معادله اول را در ضریب مجهول $y$ در معادله دوم یعنی $({eq2['b']})$ ضرب نموده و طرفین معادله دوم را در منفی ضریب مجهول $y$ در معادله اول یعنی $({-eq1['b']})$ ضرب و معادلات جدید را حاصل می‌کنیم:</p>"

        na1 = eq1['a'] * eq2['b']
        nb1 = eq1['b'] * eq2['b']
        nc1 = eq1['c'] * eq2['b']

        na2 = eq2['a'] * -eq1['b']
        nb2 = eq2['b'] * -eq1['b']
        nc2 = eq2['c'] * -eq1['b']

        html += f"$$ ({eq2['b']}) \\times ({eq1['a']}x + {eq1['b']}y = {eq1['c']}) \\implies {na1}x + {nb1}y = {nc1} $$"
        html += f"$$ ({-eq1['b']}) \\times ({eq2['a']}x + {eq2['b']}y = {eq2['c']}) \\implies {na2}x + {nb2}y = {nc2} $$"

        html += "<p><strong>گام ۲: هم‌افزایی دو معادله و حذف مجهول</strong><br>"
        html += "دو معادله‌ی اصلاح شده را به صورت ستونی با هم جمع می‌کنیم تا مجهول $y$ حذف شود:</p>"

        sa = na1 + na2
        sc = nc1 + nc2

        html += f"$$ ({na1}x + {na2}x) + ({nb1}y + {nb2}y) = {nc1} + ({nc2}) $$"
        html += f"$$ {sa}x = {sc} \\implies x = \\frac{{{sc}}}{{{sa}}} = {xSol} $$"

        html += "<p><strong>گام ۳: یافتن مجهول دیگر ($y$)</strong><br>"
        html += "با قرار دادن مقدار $x$ در معادله اول پیش از تغییر خواهیم داشت:</p>"
        html += f"$$ {eq1['a']}({xSol}) + {eq1['b']}y = {eq1['c']} $$"
        html += f"$$ {eq1['a'] * xSol:.2f} + {eq1['b']}y = {eq1['c']} \\implies {eq1['b']}y = {eq1['c'] - eq1['a'] * xSol:.2f} $$"
        html += f"$$ y = \\frac{{{eq1['c'] - eq1['a'] * xSol:.2f}}}{{{eq1['b']}}} = {ySol} $$"

    el.innerHTML = html


def buildSubstitutionSteps(eq1, eq2, xSol, ySol, solvable, infinite):
    el = document.getElementById('substitution')
    if not solvable:
        el.innerHTML = getUnsolvableTextHTML(infinite)
        return

    html = "<p>دستگاه معادلات خطی به صورت زیر داده شده است:</p>"
    html += f"$$ \\begin{{cases}} {eq1['a']}x + {eq1['b']}y = {eq1['c']} \\\\ {eq2['a']}x + {eq2['b']}y = {eq2['c']} \\end{{cases}} $$"

    if abs(eq1['b']) >= abs(eq1['a']) and abs(eq1['b']) > 1e-9:
        term_const = eq1['c'] / eq1['b']
        term_coef = -eq1['a'] / eq1['b']

        html += "<p><strong>گام ۱: محاسبه مجهول $y$ برحسب $x$ از رابطه اول</strong><br>"
        html += "متغیر $y$ را در معادله اول تنها می‌کنیم:</p>"
        html += f"$$ {eq1['b']}y = {eq1['c']} - {eq1['a']}x \\implies y = \\frac{{{eq1['c']} - {eq1['a']}x}}{{{eq1['b']}}} $$"
        html += f"$$ y = {term_const:.2f} + ({term_coef:.2f}x) $$"

        html += "<p><strong>گام ۲: جانشینی در معادله دوم</strong><br>"
        html += "عبارت به دست آمده را به جای $y$ در معادله دوم می‌گذاریم:</p>"
        html += f"$$ {eq2['a']}x + {eq2['b']}({term_const:.2f} + ({term_coef:.2f}x)) = {eq2['c']} $$"

        dist_const = eq2['b'] * term_const
        dist_coef = eq2['b'] * term_coef
        combined_coef = eq2['a'] + dist_coef

        html += "<p>با ضرب ضریب در پرانتز و ساده کردن جملات مجهول داریم:</p>"
        html += f"$$ {eq2['a']}x + ({dist_const:.2f}) + ({dist_coef:.2f}x) = {eq2['c']} $$"
        html += f"$$ ({combined_coef:.2f})x = {eq2['c'] - dist_const:.2f} \\implies x = {xSol} $$"

        html += "<p><strong>گام ۳: محاسبه متغیر دوم</strong><br>"
        html += "حالا با قرار دادن عدد $x$ در رابطه ساخته شده در گام ۱، مجهول $y$ به دست می‌آید:</p>"
        html += f"$$ y = {term_const:.2f} + ({term_coef:.2f} \\times {xSol}) = {ySol} $$"
    else:
        term_const = eq1['c'] / eq1['a']
        term_coef = -eq1['b'] / eq1['a']

        html += "<p><strong>گام ۱: محاسبه مجهول $x$ برحسب $y$ از رابطه اول</strong><br>"
        html += "متغیر $x$ را در معادله اول تنها می‌کنیم:</p>"
        html += f"$$ {eq1['a']}x = {eq1['c']} - {eq1['b']}y \\implies x = \\frac{{{eq1['c']} - {eq1['b']}y}}{{{eq1['a']}}} $$"
        html += f"$$ x = {term_const:.2f} + ({term_coef:.2f}y) $$"

        html += "<p><strong>گام ۲: جانشینی در معادله دوم</strong><br>"
        html += "رابطه حاصل را در مجهول $x$ معادله دوم قرار می‌دهیم:</p>"
        html += f"$$ {eq2['a']}({term_const:.2f} + ({term_coef:.2f}y)) + {eq2['b']}y = {eq2['c']} $$"

        dist_const = eq2['a'] * term_const
        dist_coef = eq2['a'] * term_coef
        combined_coef = eq2['b'] + dist_coef

        html += "<p>با بسط جملات داخل پرانتز و فاکتورگیری مجهول $y$ به دست می‌آید:</p>"
        html += f"$$ ({dist_const:.2f}) + ({dist_coef:.2f}y) + {eq2['b']}y = {eq2['c']} $$"
        html += f"$$ ({combined_coef:.2f})y = {eq2['c'] - dist_const:.2f} \\implies y = {ySol} $$"

        html += "<p><strong>گام ۳: به دست آوردن مجهول مابقی</strong><br>"
        html += "با قرار دادن مقدار $y$ به دست آمده در رابطه اولیه مجهول $x$ داریم:</p>"
        html += f"$$ x = {term_const:.2f} + ({term_coef:.2f} \\times {ySol}) = {xSol} $$"

    el.innerHTML = html


def buildGraphicalSteps(eq1, eq2, xSol, ySol, solvable, infinite):
    el = document.getElementById('graphical')
    if not solvable:
        el.innerHTML = getUnsolvableTextHTML(infinite)
        return

    html = "<p>هدف روش ترسیمی، بازنویسی هر دو خط به فرم شیب-عرض از مبدأ ($y = mx + d$) و مشخص کردن نقطه تقاطع است.</p>"

    html += "<p><strong>گام ۱: فرم شیب-عرض از مبدأ خط اول</strong><br>"
    if abs(eq1['b']) > 1e-9:
        m1 = -eq1['a'] / eq1['b']
        d1 = eq1['c'] / eq1['b']
        html += "معادله اول را تغییر می‌دهیم:"
        html += f"$$ y = ({m1:.2f})x + ({d1:.2f}) $$"
        html += f"شیب خط اول برابر با ${m1:.2f}$ و عرض از مبدأ آن ${d1:.2f}$ است.<br><br>"
    else:
        html += "ضریب $y$ صفر است، پس این معادله نشان‌دهنده یک خط عمودی موازای محور $y$ هاست:"
        html += f"$$ x = {eq1['c'] / eq1['a']:.2f} $$<br>"

    html += "<p><strong>گام ۲: فرم شیب-عرض از مبدأ خط دوم</strong><br>"
    if abs(eq2['b']) > 1e-9:
        m2 = -eq2['a'] / eq2['b']
        d2 = eq2['c'] / eq2['b']
        html += "معادله دوم را تغییر می‌دهیم:"
        html += f"$$ y = ({m2:.2f})x + ({d2:.2f}) $$"
        html += f"شیب خط دوم برابر با ${m2:.2f}$ و عرض از مبدأ آن ${d2:.2f}$ است.<br><br>"
    else:
        html += "ضریب $y$ صفر است، پس معادله خط نشان‌دهنده خطی کاملاً عمودی در موازات محور $y$ ها است:"
        html += f"$$ x = {eq2['c'] / eq2['a']:.2f} $$<br>"

    html += "<p><strong>گام ۳: رسم و تعیین هم‌پوشانی خطوط</strong><br>"
    html += "با پیاده‌سازی گام‌های فوق روی بوم مختصات، دو خط را ترسیم می‌کنیم. محل تلاقی و برخورد این دو خط با یکدیگر، جواب دقیق دستگاه خطی است:"
    html += f"$$ \\text{{نقطه تقاطع}}: (x, y) = ({xSol}, {ySol}) $$"

    el.innerHTML = html


def getUnsolvableTextHTML(infinite):
    if infinite:
        return '''
          <p style="color: #ea580c; font-weight: bold;">دستگاه دارای بی‌شمار جواب است.</p>
          <p>با ساده‌سازی ضرایب متوجه می‌شویم که هر دو معادله دقیقاً هم‌ارز یک خط واحد هستند. به عبارت دیگر، خطوط روی هم منطبق بوده و تمام نقاط متعلق به خط، پاسخ‌های معتبر دستگاه هستند.</p>
        '''
    else:
        return '''
          <p style="color: #dc2626; font-weight: bold;">دستگاه بدون جواب است.</p>
          <p>به علت هم‌اندازه بودن شیب دو خط مجهول و نابرابر بودن عرض از مبدأ آن‌ها، خطوط کاملاً موازی هستند و هیج تلاقی با یکدیگر نخواهند داشت.</p>
        '''


window.switchInputTemplate = switchInputTemplate
window.runEngine = runEngine
window.switchTab = switchTab

switchInputTemplate(1, 1, 1, 5)
switchInputTemplate(2, -1, 1)
runEngine()