# 文件路径及文件名: laybot_ai/jsonl.py
# 简要阐述该文件的作用: 提供 JSONL 的读写、逐行解析、容错解析与文件级处理能力，供 Batch 结果处理复用。

from __future__ import annotations
import json
from pathlib import Path
from typing import Any, Iterable, Iterator

from .exceptions import ValidationError


def iter_jsonl(text: str, *, with_line_no: bool = False) -> Iterator[Any]:
    """
    逐行解析 JSONL 文本。
    - with_line_no=False: yield dict/list/primitive
    - with_line_no=True : yield (line_no, obj)
    """
    for idx, line in enumerate(text.splitlines(), start=1):
        line = line.strip()
        if not line:
            continue
        obj = json.loads(line)
        yield (idx, obj) if with_line_no else obj


def parse_jsonl(text: str) -> list[Any]:
    return list(iter_jsonl(text))


def iter_jsonl_safe(text: str, *, with_line_no: bool = False) -> Iterator[Any]:
    """
    容错逐行解析 JSONL 文本。
    返回结构:
    {
        "ok": bool,
        "line_no": int,
        "data": Any | None,
        "error": str | None,
        "raw": str
    }
    """
    for idx, raw in enumerate(text.splitlines(), start=1):
        line = raw.strip()
        if not line:
            continue
        try:
            obj = json.loads(line)
            item = {
                "ok": True,
                "line_no": idx,
                "data": obj,
                "error": None,
                "raw": raw,
            }
        except Exception as e:
            item = {
                "ok": False,
                "line_no": idx,
                "data": None,
                "error": str(e),
                "raw": raw,
            }
        yield (idx, item) if with_line_no else item


def iter_jsonl_file(path: str | Path, *, with_line_no: bool = False, encoding="utf-8") -> Iterator[Any]:
    fp = Path(path)
    if not fp.is_file():
        raise ValidationError(f"{path} not found")

    with fp.open("r", encoding=encoding) as f:
        for idx, line in enumerate(f, start=1):
            line = line.strip()
            if not line:
                continue
            obj = json.loads(line)
            yield (idx, obj) if with_line_no else obj


def parse_jsonl_file(path: str | Path, *, encoding="utf-8") -> list[Any]:
    return list(iter_jsonl_file(path, encoding=encoding))


def iter_jsonl_file_safe(path: str | Path, *, with_line_no: bool = False, encoding="utf-8") -> Iterator[Any]:
    fp = Path(path)
    if not fp.is_file():
        raise ValidationError(f"{path} not found")

    with fp.open("r", encoding=encoding) as f:
        for idx, raw in enumerate(f, start=1):
            line = raw.strip()
            if not line:
                continue
            try:
                obj = json.loads(line)
                item = {
                    "ok": True,
                    "line_no": idx,
                    "data": obj,
                    "error": None,
                    "raw": raw.rstrip("\n"),
                }
            except Exception as e:
                item = {
                    "ok": False,
                    "line_no": idx,
                    "data": None,
                    "error": str(e),
                    "raw": raw.rstrip("\n"),
                }
            yield (idx, item) if with_line_no else item


def dump_jsonl(rows: Iterable[Any], path: str | Path, *, encoding="utf-8", ensure_ascii=False) -> str:
    fp = Path(path)
    fp.parent.mkdir(parents=True, exist_ok=True)

    with fp.open("w", encoding=encoding, newline="\n") as f:
        for row in rows:
            f.write(json.dumps(row, ensure_ascii=ensure_ascii))
            f.write("\n")

    return str(fp)
