"""Excel(.xlsx) 模板渲染：替换单元格中的占位符，整格占位且值为数字时保留数值类型。"""
from __future__ import annotations

from pathlib import Path

from openpyxl import load_workbook

from app.core.logger import get_logger
from app.core.response import AppError
from app.services.placeholder import has_placeholder, render_text, single_placeholder

logger = get_logger(__name__)


def _native_value(cell_text: str, values: dict):
    """若单元格整体恰好是一个占位符，且目标值非字符串，则返回原类型值。"""
    key = single_placeholder(cell_text)
    if key is not None and key in values and not isinstance(values[key], str):
        return values[key]
    return None


def render_xlsx(template_path: str | Path, values: dict, output_path: str | Path) -> dict:
    template_path = Path(template_path)
    output_path = Path(output_path)
    if not template_path.exists():
        raise AppError(f"模板文件不存在: {template_path}")

    wb = load_workbook(str(template_path))
    replacements = 0
    unresolved: list[str] = []

    for ws in wb.worksheets:
        for row in ws.iter_rows():
            for cell in row:
                value = cell.value
                if not isinstance(value, str) or not has_placeholder(value):
                    continue

                native = _native_value(value, values)
                if native is not None:
                    cell.value = native
                    replacements += 1
                else:
                    res = render_text(value, values)
                    cell.value = res.rendered
                    replacements += res.replacements
                    for key in res.unresolved:
                        if key not in unresolved:
                            unresolved.append(key)

    output_path.parent.mkdir(parents=True, exist_ok=True)
    wb.save(str(output_path))

    if unresolved:
        logger.warning("xlsx 有 %d 个未替换占位符: %s", len(unresolved), unresolved)
    logger.debug("xlsx 替换 %d 处 -> %s", replacements, output_path)

    return {
        "type": "xlsx",
        "output": str(output_path),
        "replacements": replacements,
        "unresolved": unresolved,
    }
