import sys
import tempfile
from pathlib import Path
import re
import asyncio
import json
from .render_mermaid import render as render_mermaid_png
import subprocess
import textwrap

def parse_to_model(md_text: str, workdir: Path):
    lines = md_text.splitlines()
    heading_re = re.compile(r'^(#{1,6})\s+(.*)$')
    bullet_re = re.compile(r'^\s*[-*]\s+(.*)$')
    number_re = re.compile(r'^\s*\d+\.\s+(.*)$')
    enum_re = re.compile(r'^\s*\d+[)\.、:]\s+(.*)$')
    anchor_re = re.compile(r'^<a\s+id\=\"([^\"]+)\"\s*>\s*</a>\s*$')
    table_row_re = re.compile(r'^\s*\|.*\|\s*$')
    table_sep_re = re.compile(r'^\s*\|(?:\s*:?-{3,}:?\s*\|)+\s*$')
    mermaid_inline_start_re = re.compile(r'^(?:\s*%%\{.*?\}%%\s*)?(?:graph|flowchart|sequenceDiagram|classDiagram|stateDiagram|erDiagram|gantt|journey|pie|mindmap)\b')

    def _png_size(p):
        import struct
        with open(p, 'rb') as f:
            sig = f.read(8)
            if sig != b'\x89PNG\r\n\x1a\n':
                return None
            while True:
                data = f.read(8)
                if not data:
                    return None
                clen, ctype = struct.unpack('>I4s', data)
                if ctype == b'IHDR':
                    wh = f.read(8)
                    w, h = struct.unpack('>II', wh)
                    return w, h
                f.seek(clen + 4, 1)

    blocks = []
    in_code = False
    code_lang = ''
    buf = []
    i = 0
    while i < len(lines):
        s = lines[i].rstrip('\n')
        s_norm = s.replace('\u00A0',' ').replace('\u200b','').replace('\ufeff','').replace('\u3000',' ')
        if s_norm.startswith('```'):
            if not in_code:
                in_code = True
                code_lang = s_norm[3:].strip()
                buf = []
                i += 1
                continue
            else:
                code_text = '\n'.join(buf)
                if code_lang.lower() == 'mermaid':
                    png_path = workdir / f'mmd_{i}.png'
                    try:
                        asyncio.run(render_mermaid_png(code_text, png_path))
                        blocks.append({"type":"image","path":str(png_path),"kind":"mermaid"})
                    except Exception:
                        blocks.append({"type":"code","lang":code_lang or "code","text":code_text})
                else:
                    is_matplotlib = (
                        'import matplotlib' in code_text
                        or 'from matplotlib' in code_text
                    )
                    if is_matplotlib:
                        png_path = workdir / f'pyplot_{i}.png'
                        ok = False
                        try:
                            runner = workdir / f'run_plot_{i}.py'
                            script = textwrap.dedent(f"""
                            import os
                            os.environ['MPLBACKEND'] = 'Agg'
                            import matplotlib
                            matplotlib.use('Agg')
                            import matplotlib.pyplot as plt
                            {code_text}
                            figs = [plt.figure(n) for n in plt.get_fignums()]
                            if not figs:
                                figs = [plt.gcf()]
                            figs[-1].savefig(r"{str(png_path)}", dpi=150, bbox_inches='tight')
                            plt.close('all')
                            """
                            )
                            runner.write_text(script, encoding='utf-8')
                            subprocess.run([sys.executable, str(runner)], check=True, timeout=60)
                            ok = png_path.exists()
                        except Exception:
                            ok = False
                        if ok:
                            blocks.append({"type":"image","path":str(png_path),"kind":"pyplot"})
                        else:
                            blocks.append({"type":"code","lang":code_lang or "code","text":code_text})
                    else:
                        blocks.append({"type":"code","lang":code_lang or "code","text":code_text})
                in_code = False
                code_lang = ''
                buf = []
                i += 1
                continue
        if in_code:
            buf.append(s_norm)
            i += 1
            continue
        m = heading_re.match(s)
        if m:
            lvl = len(m.group(1))
            text = m.group(2).strip()
            blocks.append({"type":"heading","level":lvl,"text":text})
            i += 1
            continue
        if s.strip()=='':
            i += 1
            continue
        ma = anchor_re.match(s)
        if ma:
            blocks.append({"type":"anchor","name":ma.group(1)})
            i += 1
            continue
        if mermaid_inline_start_re.match(s_norm):
            buf_inline = [s_norm]
            i += 1
            while i < len(lines):
                ns = lines[i].rstrip('\n')
                ns_norm = ns.replace('\u00A0',' ').replace('\u200b','').replace('\ufeff','').replace('\u3000',' ')
                if anchor_re.match(ns_norm) or heading_re.match(ns_norm) or ns_norm.startswith('```') or table_row_re.match(ns_norm):
                    break
                buf_inline.append(ns_norm)
                i += 1
            png_path = workdir / f'mmd_inline_{i}.png'
            try:
                asyncio.run(render_mermaid_png('\n'.join(buf_inline), png_path))
                blocks.append({"type":"image","path":str(png_path),"kind":"mermaid"})
            except Exception:
                blocks.append({"type":"text","text":"\n".join(buf_inline)})
            continue
        if table_row_re.match(s_norm) and (i + 1) < len(lines) and table_sep_re.match(lines[i+1].rstrip('\n').replace('\u00A0',' ').replace('\u200b','').replace('\ufeff','').replace('\u3000',' ')):
            tbl_lines = [s_norm]
            i += 1
            tbl_sep = lines[i].rstrip('\n')
            i += 1
            while i < len(lines):
                ns = lines[i].rstrip('\n')
                if table_row_re.match(ns):
                    tbl_lines.append(ns)
                    i += 1
                else:
                    break
            def parse_row(row):
                parts = [c.strip() for c in row.split('|')]
                if parts and parts[0] == '':
                    parts = parts[1:]
                if parts and parts[-1] == '':
                    parts = parts[:-1]
                return parts
            header = parse_row(tbl_lines[0])
            body_rows = [parse_row(r) for r in tbl_lines[1:]]
            png_path = workdir / f'table_{i}.png'
            ok_tbl = False
            try:
                runner = workdir / f'run_table_{i}.py'
                js_header = json.dumps(header, ensure_ascii=False)
                js_rows = json.dumps(body_rows, ensure_ascii=False)
                script = textwrap.dedent(f"""
                import os
                os.environ['MPLBACKEND'] = 'Agg'
                import matplotlib
                matplotlib.use('Agg')
                import matplotlib.pyplot as plt
                headers = {js_header}
                rows = {js_rows}
                fig, ax = plt.subplots(figsize=(max(6, len(headers)*1.2), max(2, len(rows)*0.7)))
                ax.axis('off')
                tbl = ax.table(cellText=rows, colLabels=headers, loc='center')
                tbl.auto_set_font_size(False)
                tbl.set_fontsize(9)
                tbl.scale(1, 1.25)
                fig.tight_layout()
                fig.savefig(r"{str(png_path)}", dpi=180, bbox_inches='tight')
                plt.close(fig)
                """
                )
                runner.write_text(script, encoding='utf-8')
                subprocess.run([sys.executable, str(runner)], check=True, timeout=60)
                ok_tbl = png_path.exists()
            except Exception:
                ok_tbl = False
            if ok_tbl:
                blocks.append({"type":"image","path":str(png_path),"kind":"table","meta":{"header":header,"rows":body_rows}})
            else:
                blocks.append({"type":"table","header":header,"rows":body_rows})
            continue
        mb = bullet_re.match(s)
        if mb:
            blocks.append({"type":"list","kind":"bullet","text":mb.group(1)})
            i += 1
            continue
        mn = number_re.match(s)
        if mn:
            blocks.append({"type":"list","kind":"number","text":mn.group(1)})
            i += 1
            continue
        me = enum_re.match(s)
        if me:
            blocks.append({"type":"list","kind":"enum","text":me.group(1)})
            i += 1
            continue
        blocks.append({"type":"text","text":s})
        i += 1
    return {"blocks": blocks}

def main():
    import argparse
    parser = argparse.ArgumentParser(
        prog='HOS_M2F.2json',
        description='Markdown 转 JSON 模型',
        epilog=(
            '示例:\n'
            '  HOS_M2F.2json input.md output.json\n'
            '相关命令:\n'
            '  HOS_M2F.2docx input.md output.docx [-p paper|patent|softcopy] [-nm] [-nc] [-q]\n'
            '  HOS_M2F.2html input.md output.html [-p wx|csdn]\n'
            '  HOS_M2F.2pdf  input.md output.pdf\n'
            '  HOS_M2F.2xml  input.md output.xml\n'
            '  HOS_M2F.2png  input.mmd output.png\n'
        ),
        formatter_class=argparse.RawTextHelpFormatter,
    )
    parser.add_argument('input', help='输入 Markdown 文件路径')
    parser.add_argument('output', help='输出 JSON 文件路径')
    args = parser.parse_args()
    inp = Path(args.input)
    outp = Path(args.output)
    text = inp.read_text(encoding='utf-8')
    with tempfile.TemporaryDirectory() as td:
        model = parse_to_model(text, Path(td))
        outp.write_text(json.dumps(model, ensure_ascii=False, indent=2), encoding='utf-8')
    print(f'Converted to JSON: {inp} -> {outp}')

if __name__ == '__main__':
    main()
