"""Tugsscript (.tugs) to Python compiler.

Tugsscript is intentionally small: its first implementation translates
Mongolian-keyword source files into readable, executable Python programs.
"""

from __future__ import annotations

import ast
from dataclasses import dataclass
import keyword
from pathlib import Path
from typing import Iterable


class TugsscriptError(Exception):
    """A source-level compiler error."""


TYPE_ALIASES = {
    "бүхэл": "бүхэл",
    "тоо": "бүхэл",
    "бутархай": "бутархай",
    "текст": "текст",
    "логик": "логик",
    "жагсаалт": "жагсаалт",
    "хоосон": "хоосон",
}

EXPRESSION_WORDS = {
    "үнэн": "True",
    "худал": "False",
    "хоосон": "None",
    "ба": "and",
    "эсвэл": "or",
    "үгүй": "not",
    "хэвлэ": "print",
    "оруул": "input",
    "урт": "len",
    "бүхэл": "__ts_int",
    "тоо": "__ts_int",
    "бутархай": "__ts_float",
    "текст": "str",
    "логик": "__ts_bool",
    "жагсаалт": "list",
}

RUNTIME = '''\
# Generated by Tugsscript compiler. Do not edit this file directly.
def __ts_type_name(value):
    if isinstance(value, bool):
        return "логик"
    if isinstance(value, int):
        return "бүхэл"
    if isinstance(value, float):
        return "бутархай"
    if isinstance(value, str):
        return "текст"
    if isinstance(value, list):
        return "жагсаалт"
    if value is None:
        return "хоосон"
    return type(value).__name__


def __ts_check(expected, value, line):
    checks = {
        "бүхэл": lambda v: isinstance(v, int) and not isinstance(v, bool),
        "бутархай": lambda v: isinstance(v, float),
        "текст": lambda v: isinstance(v, str),
        "логик": lambda v: isinstance(v, bool),
        "жагсаалт": lambda v: isinstance(v, list),
        "хоосон": lambda v: v is None,
    }
    if not checks[expected](value):
        actual = __ts_type_name(value)
        raise TypeError(f"TS мөр {line}: {expected} төрөлд {actual} утга өгч болохгүй")
    return value


def __ts_default(expected):
    return {
        "бүхэл": 0,
        "бутархай": 0.0,
        "текст": "",
        "логик": False,
        "жагсаалт": [],
        "хоосон": None,
    }[expected]


def __ts_int(value):
    return int(value)


def __ts_float(value):
    return float(value)


def __ts_bool(value):
    if isinstance(value, bool):
        return value
    if isinstance(value, str):
        normalized = value.strip().lower()
        if normalized in {"үнэн", "true", "тийм", "1"}:
            return True
        if normalized in {"худал", "false", "үгүй", "0"}:
            return False
    return bool(value)

'''


@dataclass(frozen=True)
class Token:
    text: str
    kind: str
    line: int
    column: int


@dataclass
class Block:
    kind: str
    line: int
    return_type: str | None = None


def _fail(line: int, message: str) -> TugsscriptError:
    return TugsscriptError(f"Мөр {line}: {message}")


def tokenize_line(source: str, line_number: int) -> list[Token]:
    tokens: list[Token] = []
    i = 0
    while i < len(source):
        char = source[i]
        if char.isspace():
            i += 1
            continue
        if char == "#" or source.startswith("//", i):
            break
        if char in {'"', "'"}:
            quote = char
            start = i
            i += 1
            while i < len(source):
                if source[i] == "\\":
                    i += 2
                    continue
                if source[i] == quote:
                    i += 1
                    tokens.append(Token(source[start:i], "string", line_number, start + 1))
                    break
                i += 1
            else:
                raise _fail(line_number, "текстийн хаалтын хашилт алга")
            continue
        if char.isalpha() or char == "_":
            start = i
            i += 1
            while i < len(source) and (source[i].isalnum() or source[i] == "_"):
                i += 1
            tokens.append(Token(source[start:i], "word", line_number, start + 1))
            continue
        if char.isdigit():
            start = i
            i += 1
            while i < len(source) and (source[i].isdigit() or source[i] == "."):
                i += 1
            tokens.append(Token(source[start:i], "number", line_number, start + 1))
            continue
        two = source[i : i + 2]
        if two in {"==", "!=", "<=", ">=", "+=", "-=", "*=", "/="}:
            tokens.append(Token(two, "symbol", line_number, i + 1))
            i += 2
            continue
        if char in "()[],:+-*/%=<>;":
            tokens.append(Token(char, "symbol", line_number, i + 1))
            i += 1
            continue
        raise _fail(line_number, f"танигдаагүй тэмдэг: {char!r}")
    if tokens and tokens[-1].text == ";":
        tokens.pop()
    return tokens


def _name(token: Token) -> str:
    if token.kind != "word" or not token.text.isidentifier() or keyword.iskeyword(token.text):
        raise _fail(token.line, f"буруу нэр: {token.text!r}")
    if token.text in EXPRESSION_WORDS or token.text in {
        "хувьсагч",
        "хэрэв",
        "өөр_хэрэв",
        "өөр",
        "бол",
        "төгсөв",
        "давт",
        "ээс",
        "хүртэл",
        "алхам",
        "хэдий",
        "функц",
        "буцаа",
        "зогс",
        "үргэлжлүүл",
    }:
        raise _fail(token.line, f"{token.text!r} түлхүүр үгийг нэр болгож болохгүй")
    return token.text


def _type(token: Token) -> str:
    try:
        return TYPE_ALIASES[token.text]
    except KeyError as error:
        raise _fail(token.line, f"танигдаагүй төрөл: {token.text}") from error


def _render_expression(tokens: Iterable[Token], line: int) -> str:
    result: list[str] = []
    for token in tokens:
        if token.text == "=":
            raise _fail(line, "илэрхийлэлд '=' биш '==' хэрэглэнэ")
        result.append(EXPRESSION_WORDS.get(token.text, token.text))
    if not result:
        raise _fail(line, "илэрхийлэл шаардлагатай")
    expression = " ".join(result)
    try:
        ast.parse(expression, mode="eval")
    except SyntaxError as error:
        raise _fail(line, "илэрхийллийн бүтэц буруу байна") from error
    return expression


def _find(tokens: list[Token], word: str, line: int) -> int:
    for index, token in enumerate(tokens):
        if token.text == word:
            return index
    raise _fail(line, f"{word!r} түлхүүр үг шаардлагатай")


def _split(tokens: list[Token], separator: str) -> list[list[Token]]:
    pieces: list[list[Token]] = [[]]
    depth = 0
    for token in tokens:
        if token.text in {"(", "["}:
            depth += 1
        elif token.text in {")", "]"}:
            depth -= 1
        if token.text == separator and depth == 0:
            pieces.append([])
        else:
            pieces[-1].append(token)
    return pieces


class Compiler:
    def __init__(self, filename: str = "<код>") -> None:
        self.filename = filename
        self.output: list[str] = []
        self.indent = 0
        self.blocks: list[Block] = []
        self.scopes: list[dict[str, str]] = [{}]

    def emit(self, code: str) -> None:
        self.output.append("    " * self.indent + code)

    def lookup_type(self, name: str) -> str | None:
        for scope in reversed(self.scopes):
            if name in scope:
                return scope[name]
        return None

    def compile(self, source: str) -> str:
        for line_number, raw_line in enumerate(source.splitlines(), start=1):
            tokens = tokenize_line(raw_line, line_number)
            if tokens:
                self.statement(tokens, line_number)
        if self.blocks:
            block = self.blocks[-1]
            raise _fail(block.line, f"{block.kind!r} блок 'төгсөв'-өөр хаагдаагүй")
        return RUNTIME + "\n".join(self.output) + "\n"

    def statement(self, tokens: list[Token], line: int) -> None:
        first = tokens[0].text
        if first == "хувьсагч":
            self.declaration(tokens, line)
        elif first == "хэрэв":
            self.if_statement(tokens, line)
        elif first == "өөр_хэрэв":
            self.elif_statement(tokens, line)
        elif first == "өөр":
            self.else_statement(tokens, line)
        elif first == "давт":
            self.for_statement(tokens, line)
        elif first == "хэдий":
            self.while_statement(tokens, line)
        elif first == "функц":
            self.function_statement(tokens, line)
        elif first == "буцаа":
            self.return_statement(tokens, line)
        elif first == "төгсөв":
            self.end_statement(tokens, line)
        elif first in {"зогс", "үргэлжлүүл"}:
            self.loop_control(tokens, line)
        elif len(tokens) >= 3 and tokens[1].text in {"=", "+=", "-=", "*=", "/="}:
            self.assignment(tokens, line)
        else:
            self.emit(_render_expression(tokens, line))

    def declaration(self, tokens: list[Token], line: int) -> None:
        if len(tokens) < 4:
            raise _fail(line, "хувьсагч зарлахдаа нэр, төрөл өгнө")
        name = _name(tokens[1])
        if name in self.scopes[-1]:
            raise _fail(line, f"{name!r} хувьсагч давхар зарлагдсан")
        if tokens[2].text != ":":
            raise _fail(line, "төрлийг ':' тэмдгийн дараа бичнэ")
        variable_type = _type(tokens[3])
        if len(tokens) == 4:
            expression = f"__ts_default({variable_type!r})"
        elif tokens[4].text == "=":
            expression = _render_expression(tokens[5:], line)
        else:
            raise _fail(line, "анхны утгын өмнө '=' шаардлагатай")
        self.scopes[-1][name] = variable_type
        self.emit(f"{name} = __ts_check({variable_type!r}, {expression}, {line})")

    def assignment(self, tokens: list[Token], line: int) -> None:
        name = _name(tokens[0])
        variable_type = self.lookup_type(name)
        if variable_type is None:
            raise _fail(line, f"{name!r} хувьсагч эхлээд зарлагдах ёстой")
        operator = tokens[1].text
        expression = _render_expression(tokens[2:], line)
        if operator == "=":
            value = expression
        else:
            value = f"{name} {operator[0]} ({expression})"
        self.emit(f"{name} = __ts_check({variable_type!r}, {value}, {line})")

    def if_statement(self, tokens: list[Token], line: int) -> None:
        if tokens[-1].text != "бол":
            raise _fail(line, "'хэрэв' нөхцөлийн төгсгөлд 'бол' бичнэ")
        condition = _render_expression(tokens[1:-1], line)
        self.emit(f"if {condition}:")
        self.blocks.append(Block("хэрэв", line))
        self.indent += 1

    def elif_statement(self, tokens: list[Token], line: int) -> None:
        if not self.blocks or self.blocks[-1].kind not in {"хэрэв", "өөр_хэрэв"}:
            raise _fail(line, "'өөр_хэрэв' нь 'хэрэв'-ийн дотор байна")
        if tokens[-1].text != "бол":
            raise _fail(line, "'өөр_хэрэв' нөхцөлийн төгсгөлд 'бол' бичнэ")
        self.indent -= 1
        condition = _render_expression(tokens[1:-1], line)
        self.emit(f"elif {condition}:")
        self.indent += 1
        self.blocks[-1].kind = "өөр_хэрэв"

    def else_statement(self, tokens: list[Token], line: int) -> None:
        if len(tokens) != 1:
            raise _fail(line, "'өөр' мөрөнд нэмэлт утга бичихгүй")
        if not self.blocks or self.blocks[-1].kind not in {"хэрэв", "өөр_хэрэв"}:
            raise _fail(line, "'өөр' нь 'хэрэв'-ийн дотор байна")
        self.indent -= 1
        self.emit("else:")
        self.indent += 1
        self.blocks[-1].kind = "өөр"

    def for_statement(self, tokens: list[Token], line: int) -> None:
        if len(tokens) < 7 or tokens[2].text != "=":
            raise _fail(line, "давталт: давт i = эхлэл ээс төгсгөл хүртэл")
        name = _name(tokens[1])
        from_index = _find(tokens, "ээс", line)
        to_index = _find(tokens, "хүртэл", line)
        if from_index <= 3 or to_index <= from_index + 1:
            raise _fail(line, "давталтын хязгаар буруу байна")
        step_index = next(
            (i for i, token in enumerate(tokens) if token.text == "алхам"),
            len(tokens),
        )
        start = _render_expression(tokens[3:from_index], line)
        end = _render_expression(tokens[from_index + 1 : to_index], line)
        step = _render_expression(tokens[step_index + 1 :], line) if step_index < len(tokens) else "1"
        self.scopes[-1][name] = "бүхэл"
        self.emit(f"for {name} in range({start}, ({end}) + (1 if ({step}) > 0 else -1), {step}):")
        self.blocks.append(Block("давт", line))
        self.indent += 1

    def while_statement(self, tokens: list[Token], line: int) -> None:
        if tokens[-1].text != "бол":
            raise _fail(line, "'хэдий' нөхцөлийн төгсгөлд 'бол' бичнэ")
        condition = _render_expression(tokens[1:-1], line)
        self.emit(f"while {condition}:")
        self.blocks.append(Block("хэдий", line))
        self.indent += 1

    def function_statement(self, tokens: list[Token], line: int) -> None:
        if len(tokens) < 4:
            raise _fail(line, "функцийн нэр ба хаалт шаардлагатай")
        name = _name(tokens[1])
        if tokens[2].text != "(":
            raise _fail(line, "функцийн параметр '(' тэмдгээр эхэлнэ")
        close = _find(tokens, ")", line)
        if close < 3:
            raise _fail(line, "функцийн хаалт буруу байна")
        return_type = "хоосон"
        if close + 1 < len(tokens):
            if close + 2 != len(tokens) - 1 or tokens[close + 1].text != ":":
                raise _fail(line, "буцах төрлийг ': төрөл' хэлбэрээр бичнэ")
            return_type = _type(tokens[close + 2])
        parameters: list[tuple[str, str]] = []
        for parameter in _split(tokens[3:close], ","):
            if not parameter:
                continue
            if len(parameter) != 3 or parameter[1].text != ":":
                raise _fail(line, "параметрийг 'нэр: төрөл' хэлбэрээр бичнэ")
            parameters.append((_name(parameter[0]), _type(parameter[2])))
        rendered = ", ".join(parameter[0] for parameter in parameters)
        self.emit(f"def {name}({rendered}):")
        self.blocks.append(Block("функц", line, return_type))
        self.indent += 1
        self.scopes.append({parameter[0]: parameter[1] for parameter in parameters})
        for parameter, parameter_type in parameters:
            self.emit(f"{parameter} = __ts_check({parameter_type!r}, {parameter}, {line})")

    def return_statement(self, tokens: list[Token], line: int) -> None:
        function = next((block for block in reversed(self.blocks) if block.kind == "функц"), None)
        if function is None:
            raise _fail(line, "'буцаа' зөвхөн функц дотор хэрэглэгдэнэ")
        if function.return_type == "хоосон" and len(tokens) == 1:
            self.emit("return None")
            return
        expression = _render_expression(tokens[1:], line)
        self.emit(f"return __ts_check({function.return_type!r}, {expression}, {line})")

    def loop_control(self, tokens: list[Token], line: int) -> None:
        if len(tokens) != 1:
            raise _fail(line, f"{tokens[0].text!r} дангаараа мөрөнд бичигдэнэ")
        if not any(block.kind in {"давт", "хэдий"} for block in self.blocks):
            raise _fail(line, f"{tokens[0].text!r} зөвхөн давталт дотор хэрэглэгдэнэ")
        self.emit("break" if tokens[0].text == "зогс" else "continue")

    def end_statement(self, tokens: list[Token], line: int) -> None:
        if len(tokens) != 1:
            raise _fail(line, "'төгсөв' дангаараа мөрөнд бичигдэнэ")
        if not self.blocks:
            raise _fail(line, "хаах блок байхгүй байна")
        block = self.blocks.pop()
        self.indent -= 1
        if block.kind == "функц":
            if block.return_type == "хоосон":
                self.emit("return None")
            self.scopes.pop()


def compile_source(source: str, filename: str = "<код>") -> str:
    return Compiler(filename).compile(source)


def compile_file(source_path: str | Path, output_path: str | Path | None = None) -> Path:
    source = Path(source_path)
    if source.suffix != ".tugs":
        raise TugsscriptError("Оролтын файл .tugs өргөтгөлтэй байна")
    target = Path(output_path) if output_path else source.with_suffix(".py")
    compiled = compile_source(source.read_text(encoding="utf-8"), str(source))
    target.write_text(compiled, encoding="utf-8")
    return target
