#!/usr/bin/env python3
"""只读分析 xlsx 每个 sheet 的长内容单元格：按合并宽度+字号估算所需视觉行数与建议行高，
与当前行高对比，标出可能截断的行。不修改文件。

用法: python3 xlsx-rowheight-analyze.py <文件.xlsx> [最小字符阈值,默认80]

背景见 references/onlyoffice-xlsx-rowheight-rendering.md：
- 409.5/409.6pt 是 OnlyOffice 网页编辑器的 clamp 值，不是 xlsx 格式天花板
- openpyxl 从文件层可写 >409.5 且 x2t 引擎不 clamp
经验系数：中文每字≈2.1 宽度单位（西文≈1.05），每视觉行≈15.5pt(10pt字)。
"""
import sys, math
import openpyxl
from openpyxl.utils import get_column_letter, range_boundaries

DEFAULT_WIDTH = 8.43

def col_width(ws, col_letter):
    dim = ws.column_dimensions.get(col_letter)
    return dim.width if (dim and dim.width) else DEFAULT_WIDTH

def merged_info(ws, coord):
    for m in ws.merged_cells.ranges:
        if coord in m:
            c0, r0, c1, r1 = range_boundaries(str(m))
            total = sum(col_width(ws, get_column_letter(c)) for c in range(c0, c1 + 1))
            return total, str(m)
    col = ''.join(filter(str.isalpha, coord))
    return col_width(ws, col), None

def estimate_height(text, total_width, font_sz):
    cap = max(total_width, 1)
    visual_rows = 0
    for line in text.split("\n"):
        w = sum((2.1 if ord(ch) > 0x2000 else 1.05) for ch in line)
        visual_rows += max(1, math.ceil(w / cap))
    per_row = 15.5 if (font_sz or 11) <= 11 else (font_sz * 1.4)
    return visual_rows, math.ceil(visual_rows * per_row + 8)

def main():
    if len(sys.argv) < 2:
        print(__doc__); sys.exit(1)
    path = sys.argv[1]
    threshold = int(sys.argv[2]) if len(sys.argv) > 2 else 80
    wb = openpyxl.load_workbook(path)
    print(f"FILE: {path}\nSHEETS: {wb.sheetnames}\n")
    for ws in wb.worksheets:
        heights = {r: round(d.height, 1) for r, d in ws.row_dimensions.items() if d.height}
        flagged = []
        for row in ws.iter_rows():
            for cell in row:
                if cell.value and isinstance(cell.value, str) and len(cell.value) > threshold:
                    tw, mrange = merged_info(ws, cell.coordinate)
                    fsz = cell.font.sz or 11
                    vr, sug = estimate_height(cell.value, tw, fsz)
                    cur = heights.get(cell.row)
                    short = cur is not None and cur < sug
                    flagged.append((cell.coordinate, mrange, len(cell.value),
                                    cell.value.count(chr(10)) + 1, round(tw, 1), vr, sug, cur, short))
        if not flagged:
            continue
        print(f"===== {ws.title} (max_row={ws.max_row}) =====")
        for co, mr, ln, ll, tw, vr, sug, cur, short in flagged:
            mark = "  ⚠️可能截断" if short else ""
            print(f"  {co} merge={mr} chars={ln} lines={ll} w={tw} "
                  f"=> est_rows={vr} SUGGEST={sug}pt  current={cur}{mark}")
        print()

if __name__ == "__main__":
    main()
