"""
طراحی شده توسط محمد نورائی
تمامی حقوق محفوظ است (C) ۱۴۰۵ - تا پایان زمان

Written by Mohammad Nouraei.
CopyRight (C) 2026 - Until end of time
"""

import re
import math
from fractions import Fraction
from js import window, document, katex
from pyodide.ffi import to_js

def format_raw_to_latex(s: str) -> str:
    s = s.replace(" ", "")
    s = s.replace(">=", r" \ge ").replace("<=", r" \le ")
    s = s.replace("=>", r" \ge ").replace("=<", r" \le ")
    
    s = re.sub(r'([a-zA-Z0-9.-]*)\(([^()]+)\)([a-zA-Z0-9.-]*)/\(([^()]+)\)', r'\\frac{\1(\2)\3}{\4}', s)
    s = re.sub(r'([a-zA-Z0-9.-]*)\(([^()]+)\)([a-zA-Z0-9.-]*)/([a-zA-Z0-9.-]+)', r'\\frac{\1(\2)\3}{\4}', s)
    s = re.sub(r'([a-zA-Z0-9.-]+)/\(([^()]+)\)', r'\\frac{\1}{\2}', s)
    s = re.sub(r'([a-zA-Z0-9.-]+)/([a-zA-Z0-9.-]+)', r'\\frac{\1}{\2}', s)
    s = re.sub(r'\\frac\{\(([^()]+)\)\}', r'\\frac{\1}', s)
    
    s = s.replace('*', '')
    return s

def parse_linear_expr(expr_str: str):
    if not expr_str:
        return Fraction(0), Fraction(0)
    terms = re.findall(r'[+-]?[^+-]+', expr_str)
    A, B = Fraction(0), Fraction(0)
    for term in terms:
        if 'x' in term:
            clean = term.replace('x', '').replace('*', '')
            if clean in ['', '+']: coeff = Fraction(1)
            elif clean == '-': coeff = Fraction(-1)
            else:
                if clean.startswith('/'): clean = '1' + clean
                elif clean.startswith('+/'): clean = '1' + clean[1:]
                elif clean.startswith('-/'): clean = '-1' + clean[2:]
                coeff = Fraction(clean)
            A += coeff
        else:
            B += Fraction(term)
    return A, B

def expand_parentheses(s: str) -> str:
    pattern = r'([+-]?(?:\d+(?:\.\d+)?(?:/\d+)?\*?)?)\(([^()]+)\)(?:\*?([+-]?\d+(?:\.\d+)?(?:/\d+)?))?(?:/([+-]?\d+(?:\.\d+)?(?:/\d+)?))?'
    
    def replacer(match):
        left_c = match.group(1)
        inside = match.group(2)
        right_c = match.group(3)
        div_c = match.group(4)
        
        mult = Fraction(1)
        if left_c and left_c not in ['+', '-']:
            mult *= Fraction(left_c.replace('*', ''))
        elif left_c == '-': mult *= Fraction(-1)
            
        if right_c: mult *= Fraction(right_c.replace('*', ''))
        if div_c: mult /= Fraction(div_c)
            
        sub_A, sub_B = parse_linear_expr(inside)
        res_A, res_B = sub_A * mult, sub_B * mult
        
        res = ""
        if res_A != 0: res += f"+{res_A}x" if res_A > 0 else f"{res_A}x"
        if res_B != 0: res += f"+{res_B}" if res_B > 0 else f"{res_B}"
        if not res: res = "+0"
        return res

    while '(' in s and ')' in s:
        new_s = re.sub(pattern, replacer, s)
        if new_s == s: break
        s = new_s
    return s

def normalize_input(s: str) -> str:
    for i, p_digit in enumerate("۰۱۲۳۴۵۶۷۸۹"): s = s.replace(p_digit, str(i))
    for i, a_digit in enumerate("٠١٢٣٤٥٦٧٨٩"): s = s.replace(a_digit, str(i))
    
    s = s.replace(" ", "").replace("÷", "/").replace("×", "*").replace("٫", ".").replace(",", ".")
    s = s.replace("≥", ">=").replace("≤", "<=").replace("=>", ">=").replace("=<", "<=")

    while "--" in s or "+-" in s or "-+" in s or "++" in s:
        s = s.replace("--", "+").replace("+-", "-").replace("-+", "-").replace("++", "+")
    s = s.replace("/+", "/")
    s = expand_parentheses(s)
    
    while "--" in s or "+-" in s or "-+" in s or "++" in s:
        s = s.replace("--", "+").replace("+-", "-").replace("-+", "-").replace("++", "+")
    return s

def fmt_frac_latex(f: Fraction) -> str:
    if f.denominator == 1: return str(f.numerator)
    if f.numerator < 0: return rf"-\frac{{{abs(f.numerator)}}}{{{f.denominator}}}"
    return rf"\frac{{{f.numerator}}}{{{f.denominator}}}"

def fmt_expr_latex(a: Fraction, b: Fraction) -> str:
    parts = []
    if a != 0:
        if a == 1: parts.append("x")
        elif a == -1: parts.append("-x")
        else: parts.append(f"{fmt_frac_latex(a)}x")
    if b != 0 or not parts:
        if not parts: parts.append(fmt_frac_latex(b))
        else:
            if b > 0: parts.append(f"+ {fmt_frac_latex(b)}")
            else: parts.append(f"- {fmt_frac_latex(abs(b))}")
    return " ".join(parts)

def getLatexOp(op):
    mapping = {'>': '>', '<': '<', '>=': r'\ge', '<=': r'\le'}
    return mapping.get(op, op)

def getFlippedOp(op):
    mapping = {'>': '<', '<': '>', '>=': '<=', '<=': '>='}
    return mapping.get(op, op)

def getPrettyOp(op):
    mapping = {'>': '>', '<': '<', '>=': '≥', '<=': '≤'}
    return mapping.get(op, op)

def render_steps(steps):
    stepsOutput = document.getElementById('steps-output')
    for step in steps:
        container = document.createElement('div')
        container.className = 'step-container'

        label = document.createElement('div')
        is_warning = step.get('isWarning', False)
        label.className = 'step-label' + (' warning-label' if is_warning else '')
        label.innerText = step['label']

        mathBox = document.createElement('div')
        mathBox.className = 'math-box'

        container.appendChild(label)
        container.appendChild(mathBox)
        stepsOutput.appendChild(container)

        options = to_js({'displayMode': True}, dict_converter=window.Object.fromEntries)
        katex.render(step['math'], mathBox, options)

def drawArrow(ctx, x, y, angle, color="#374151"):
    ctx.save()
    ctx.translate(x, y)
    ctx.rotate(angle)
    ctx.beginPath()
    ctx.moveTo(0, 0)
    ctx.lineTo(-8, -4)
    ctx.lineTo(-8, 4)
    ctx.closePath()
    ctx.fillStyle = color
    ctx.fill()
    ctx.restore()

def initAxis(ctx, w, h, centerY):
    ctx.clearRect(0, 0, w, h)
    ctx.beginPath()
    ctx.strokeStyle = "#374151"
    ctx.lineWidth = 2
    ctx.moveTo(25, centerY)
    ctx.lineTo(w - 25, centerY)
    ctx.stroke()
    drawArrow(ctx, 25, centerY, math.pi)
    drawArrow(ctx, w - 25, centerY, 0)

def drawOnAxis(value, op, is_all=False, is_none=False):
    canvas = document.getElementById('axisCanvas')
    ctx = canvas.getContext('2d')
    w, h = canvas.width, canvas.height
    centerX, centerY = w / 2.0, h / 2.0 + 10.0
    initAxis(ctx, w, h, centerY)

    if is_none:
        ctx.font, ctx.fillStyle, ctx.textAlign = "14px Tahoma", "#ef4444", "center"
        ctx.fillText("مجموعه جواب تهی است (هیچ خطی روی محور رسم نمی‌شود)", centerX, centerY - 25)
        return

    if is_all:
        ctx.beginPath()
        ctx.strokeStyle, ctx.lineWidth = "#3b82f6", 6
        ctx.moveTo(30, centerY - 15)
        ctx.lineTo(w - 30, centerY - 15)
        ctx.stroke()
        drawArrow(ctx, 30, centerY - 15, math.pi, color="#3b82f6")
        drawArrow(ctx, w - 30, centerY - 15, 0, color="#3b82f6")
        ctx.font, ctx.fillStyle, ctx.textAlign = "14px Tahoma", "#2563eb", "center"
        ctx.fillText("مجموعه جواب: تمام اعداد حقیقی (ℝ)", centerX, centerY - 32)
        return

    num_val, scale = float(value), 45.0
    center_int = math.floor(num_val)
    ctx.font, ctx.textAlign = "12px Tahoma", "center"
    ctx.fillStyle, ctx.strokeStyle = "#374151", "#374151"

    for i in range(center_int - 5, center_int + 6):
        x = centerX + (i - center_int) * scale
        if 30 <= x <= w - 30:
            ctx.beginPath()
            ctx.moveTo(x, centerY - 5); ctx.lineTo(x, centerY + 5); ctx.stroke()
            ctx.fillText(str(i), x, centerY + 20)

    solX = centerX + (num_val - center_int) * scale
    isRight, isClosed, rayY = '>' in op, '=' in op, centerY - 15

    ctx.beginPath()
    ctx.strokeStyle, ctx.lineWidth = "#3b82f6", 5
    ctx.moveTo(solX, rayY)
    ctx.lineTo(w - 25 if isRight else 25, rayY)
    ctx.stroke()
    drawArrow(ctx, w - 25 if isRight else 25, rayY, 0 if isRight else math.pi, color="#3b82f6")

    ctx.beginPath()
    ctx.arc(solX, rayY, 6, 0, math.pi * 2)
    ctx.fillStyle = "#3b82f6" if isClosed else "#ffffff"
    ctx.strokeStyle, ctx.lineWidth = "#3b82f6", 2
    ctx.fill(); ctx.stroke()

    ctx.font, ctx.fillStyle = "bold 12px Tahoma", "#1d4ed8"
    val_str = str(value) if value.denominator == 1 else f"{value.numerator}/{value.denominator}"
    ctx.fillText(f"x = {val_str}", solX, rayY - 12)

def drawInterval(v_min, v_max, min_closed, max_closed):
    canvas = document.getElementById('axisCanvas')
    ctx = canvas.getContext('2d')
    w, h = canvas.width, canvas.height
    centerX, centerY = w / 2.0, h / 2.0 + 10.0
    initAxis(ctx, w, h, centerY)
    
    num_min, num_max = float(v_min), float(v_max)
    diff = num_max - num_min
    scale = min(45.0, (w - 120) / diff) if diff > 0 else 45.0
    center_val = (num_min + num_max) / 2.0
    center_int = math.floor(center_val)
    
    ctx.font, ctx.textAlign = "12px Tahoma", "center"
    ctx.fillStyle, ctx.strokeStyle = "#374151", "#374151"

    num_ticks = int((w / scale) / 2) + 2
    for i in range(center_int - num_ticks, center_int + num_ticks + 1):
        x = centerX + (i - center_val) * scale
        if 30 <= x <= w - 30:
            ctx.beginPath()
            ctx.moveTo(x, centerY - 5); ctx.lineTo(x, centerY + 5); ctx.stroke()
            ctx.fillText(str(i), x, centerY + 20)

    min_X = centerX + (num_min - center_val) * scale
    max_X = centerX + (num_max - center_val) * scale
    rayY = centerY - 15

    ctx.beginPath()
    ctx.strokeStyle, ctx.lineWidth = "#3b82f6", 5
    ctx.moveTo(min_X, rayY)
    ctx.lineTo(max_X, rayY)
    ctx.stroke()

    for x_pos, is_closed, val in [(min_X, min_closed, v_min), (max_X, max_closed, v_max)]:
        ctx.beginPath()
        ctx.arc(x_pos, rayY, 6, 0, math.pi * 2)
        ctx.fillStyle = "#3b82f6" if is_closed else "#ffffff"
        ctx.strokeStyle, ctx.lineWidth = "#3b82f6", 2
        ctx.fill(); ctx.stroke()
        
        ctx.font, ctx.fillStyle = "bold 12px Tahoma", "#1d4ed8"
        val_str = str(val) if val.denominator == 1 else f"{val.numerator}/{val.denominator}"
        ctx.fillText(val_str, x_pos, rayY - 12)


def processSingleInequality(L_str, op, R_str, raw_input, steps, banner):
    L_A, L_B = parse_linear_expr(L_str)
    R_A, R_B = parse_linear_expr(R_str)

    steps.append({'label': "نامعادله وارد شده:", 'math': format_raw_to_latex(raw_input)})

    expanded_L, expanded_R = fmt_expr_latex(L_A, L_B), fmt_expr_latex(R_A, R_B)
    if '(' in raw_input or '/' in raw_input:
        steps.append({'label': "ساده‌سازی و بسط اولیه عبارت:", 'math': f"{expanded_L if expanded_L else '0'} {getLatexOp(op)} {expanded_R if expanded_R else '0'}"})

    net_A, net_C = L_A - R_A, R_B - L_B
    if R_A != 0 or L_B != 0:
        steps.append({'label': "انتقال جملات شامل x به چپ و اعداد به راست:", 'math': f"{fmt_expr_latex(net_A, Fraction(0))} {getLatexOp(op)} {fmt_frac_latex(net_C)}"})

    if net_A == 0:
        is_true = (0 > net_C) if op == '>' else (0 < net_C) if op == '<' else (0 >= net_C) if op == '>=' else (0 <= net_C)
        if is_true:
            banner.innerText = "جواب نهایی: تمامی اعداد حقیقی (ℝ)"
            drawOnAxis(None, op, is_all=True)
            steps.append({'label': "نتیجه‌گیری: نامعادله همواره برقرار است.", 'math': r"x \in \mathbb{R}"})
        else:
            banner.innerText = "جواب نهایی: فاقد جواب (تهی ∅)"
            drawOnAxis(None, op, is_none=True)
            steps.append({'label': "نتیجه‌گیری: نامعادله جوابی ندارد.", 'math': r"x \in \emptyset"})
        render_steps(steps)
        return

    finalOp = getFlippedOp(op) if net_A < 0 else op
    if net_A < 0:
        steps.append({'label': "هشدار: چون ضریب x منفی است، جهت نامعادله عوض می‌شود:", 'math': f"{getLatexOp(op)} \\implies {getLatexOp(finalOp)}", 'isWarning': True})

    final_val = net_C / net_A
    approx_str = f" \\quad (x {getLatexOp(finalOp)} {float(final_val):.2f})" if final_val.denominator != 1 else ""
    steps.append({'label': "جواب نهایی:", 'math': f"x {getLatexOp(finalOp)} {fmt_frac_latex(final_val)}{approx_str}"})
    
    display_text = f"x {getPrettyOp(finalOp)} {final_val.numerator}/{final_val.denominator}" if final_val.denominator != 1 else f"x {getPrettyOp(finalOp)} {final_val}"
    banner.innerText = f"جواب نهایی: {display_text}"
    
    render_steps(steps)
    drawOnAxis(final_val, finalOp)

def processDoubleInequality(L_str, op1, M_str, op2, R_str, raw_input, steps, banner):
    L_A, L_B = parse_linear_expr(L_str)
    M_A, M_B = parse_linear_expr(M_str)
    R_A, R_B = parse_linear_expr(R_str)

    steps.append({'label': "نامعادله دوگانه وارد شده:", 'math': format_raw_to_latex(raw_input)})

    if L_A != 0 or R_A != 0:
        window.alert("در این نسخه، برای حل نامعادلات دوگانه، متغیر x باید فقط در بخش میانی باشد (مثل 2 < 3x+1 < 5).")
        return
    if M_A == 0:
        window.alert("متغیری در بخش میانی یافت نشد!")
        return

    if '(' in raw_input or '/' in raw_input:
        steps.append({'label': "ساده‌سازی و بسط اولیه عبارت میانی:", 'math': f"{fmt_frac_latex(L_B)} {getLatexOp(op1)} {fmt_expr_latex(M_A, M_B)} {getLatexOp(op2)} {fmt_frac_latex(R_B)}"})

    net_L, net_R = L_B - M_B, R_B - M_B
    if M_B != 0:
        steps.append({'label': "انتقال عدد ثابت وسط با تغییر علامت به هر دو طرف:", 'math': f"{fmt_frac_latex(net_L)} {getLatexOp(op1)} {fmt_expr_latex(M_A, Fraction(0))} {getLatexOp(op2)} {fmt_frac_latex(net_R)}"})

    ans_L, ans_R = net_L / M_A, net_R / M_A
    final_op1, final_op2 = op1, op2

    if M_A < 0:
        steps.append({'label': "هشدار: تقسیم بر ضریب منفی، جهت‌ها را تغییر می‌دهد:", 'math': f"{fmt_frac_latex(ans_L)} {getLatexOp(getFlippedOp(op1))} x {getLatexOp(getFlippedOp(op2))} {fmt_frac_latex(ans_R)}", 'isWarning': True})
        ans_L, ans_R = ans_R, ans_L
        final_op1, final_op2 = op2, op1
        steps.append({'label': "مرتب‌سازی از کوچک به بزرگ (استاندارد بازه):", 'math': f"{fmt_frac_latex(ans_L)} {getLatexOp(final_op1)} x {getLatexOp(final_op2)} {fmt_frac_latex(ans_R)}"})
    elif M_A != 1:
        steps.append({'label': "تقسیم همه طرف‌ها بر ضریب متغیر:", 'math': f"{fmt_frac_latex(ans_L)} {getLatexOp(final_op1)} x {getLatexOp(final_op2)} {fmt_frac_latex(ans_R)}"})

    if float(ans_L) > float(ans_R) or (ans_L == ans_R and ('=' not in final_op1 or '=' not in final_op2)):
        banner.innerText = "جواب نهایی: فاقد جواب (تهی ∅)"
        drawOnAxis(None, None, is_none=True)
    else:
        approx_str = f" \\quad ({float(ans_L):.2f} {getLatexOp(final_op1)} x {getLatexOp(final_op2)} {float(ans_R):.2f})" if ans_L.denominator != 1 or ans_R.denominator != 1 else ""
        steps.append({'label': "جواب نهایی:", 'math': f"{fmt_frac_latex(ans_L)} {getLatexOp(final_op1)} x {getLatexOp(final_op2)} {fmt_frac_latex(ans_R)} {approx_str}"})
        
        display_L = ans_L if ans_L.denominator == 1 else f"{ans_L.numerator}/{ans_L.denominator}"
        display_R = ans_R if ans_R.denominator == 1 else f"{ans_R.numerator}/{ans_R.denominator}"
        banner.innerText = f"جواب نهایی:  {display_L} {getPrettyOp(final_op1)} x {getPrettyOp(final_op2)} {display_R}"
        
        drawInterval(ans_L, ans_R, '=' in final_op1, '=' in final_op2)
        
    render_steps(steps)

def processInequality(*args):
    raw_input = document.getElementById('user-input').value
    stepsOutput = document.getElementById('steps-output')
    banner = document.getElementById('final-res-banner')
    stepsOutput.innerHTML = ''
    steps = []

    try:
        clean_str = normalize_input(raw_input)
        tokens = [t.strip() for t in re.split(r'(>=|<=|>|<)', clean_str) if t.strip()]
        
        if len(tokens) == 5:
            processDoubleInequality(tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], raw_input, steps, banner)
        elif len(tokens) == 3:
            processSingleInequality(tokens[0], tokens[1], tokens[2], raw_input, steps, banner)
        else:
            window.alert("فرمت نامعادله درست نیست. فقط یک یا دو علامت کوچکتر/بزرگتر مجاز است.")
    except Exception:
        window.alert("خطا در تحلیل ریاضی. لطفاً ورودی را بررسی کنید.")

def on_key_down(event):
    if event.key == "Enter": processInequality()

window.processInequality = processInequality
document.getElementById('user-input').addEventListener('keydown', to_js(on_key_down))

processInequality()