import json
import re
import sys

INLINE_CODE = re.compile(r"`([^`]+)`")
BOLD = re.compile(r"\*\*([^*]+)\*\*")


def text_node(text, marks=None):
    node = {"type": "text", "text": text}
    if marks:
        node["marks"] = [{"type": m} for m in marks]
    return node


def inline_nodes(text):
    nodes = []
    pos = 0
    pattern = re.compile(r"`([^`]+)`|\*\*([^*]+)\*\*")
    for m in pattern.finditer(text):
        if m.start() > pos:
            nodes.append(text_node(text[pos:m.start()]))
        if m.group(1) is not None:
            nodes.append(text_node(m.group(1), ["code"]))
        else:
            nodes.append(text_node(m.group(2), ["strong"]))
        pos = m.end()
    if pos < len(text):
        nodes.append(text_node(text[pos:]))
    return nodes or [text_node("")]


def paragraph(text):
    return {"type": "paragraph", "content": inline_nodes(text)}


def heading(level, text):
    return {"type": "heading", "attrs": {"level": level}, "content": inline_nodes(text)}


def code_block(lang, lines):
    attrs = {"language": lang} if lang else {}
    return {"type": "codeBlock", "attrs": attrs, "content": [text_node("\n".join(lines))]}


def list_node(items, ordered):
    return {
        "type": "orderedList" if ordered else "bulletList",
        "content": [{"type": "listItem", "content": [paragraph(i)]} for i in items],
    }


def cell(kind, text):
    return {"type": kind, "attrs": {}, "content": [paragraph(text)]}


def table(rows):
    out = {"type": "table", "attrs": {"layout": "default"}, "content": []}
    for i, row in enumerate(rows):
        kind = "tableHeader" if i == 0 else "tableCell"
        out["content"].append({"type": "tableRow", "content": [cell(kind, c) for c in row]})
    return out


def split_row(line):
    return [c.strip() for c in line.strip().strip("|").split("|")]


def build_adf(md):
    lines = md.splitlines()
    content = []
    i = 0
    while i < len(lines):
        line = lines[i]
        if not line.strip():
            i += 1
            continue
        if line.startswith("```"):
            lang = line[3:].strip()
            body = []
            i += 1
            while i < len(lines) and not lines[i].startswith("```"):
                body.append(lines[i])
                i += 1
            i += 1
            content.append(code_block(lang, body))
            continue
        m = re.match(r"^(#{1,6})\s+(.*)", line)
        if m:
            content.append(heading(len(m.group(1)), m.group(2).strip()))
            i += 1
            continue
        if line.lstrip().startswith("|"):
            rows = []
            while i < len(lines) and lines[i].lstrip().startswith("|"):
                if not re.match(r"^\s*\|[\s:|-]+\|\s*$", lines[i]):
                    rows.append(split_row(lines[i]))
                i += 1
            content.append(table(rows))
            continue
        if re.match(r"^\s*- ", line):
            items = []
            while i < len(lines) and re.match(r"^\s*- ", lines[i]):
                items.append(re.sub(r"^\s*- ", "", lines[i]).strip())
                i += 1
            content.append(list_node(items, ordered=False))
            continue
        if re.match(r"^\s*\d+\.\s", line):
            items = []
            while i < len(lines) and re.match(r"^\s*\d+\.\s", lines[i]):
                items.append(re.sub(r"^\s*\d+\.\s", "", lines[i]).strip())
                i += 1
            content.append(list_node(items, ordered=True))
            continue
        para = [line.strip()]
        i += 1
        while i < len(lines) and lines[i].strip() and not re.match(r"^(#|```|\s*-\s|\s*\d+\.\s|\s*\|)", lines[i]):
            para.append(lines[i].strip())
            i += 1
        content.append(paragraph(" ".join(para)))
    return {"type": "doc", "version": 1, "content": content}


def append_table_rows(field_json_path, rows_md_path):
    field = json.load(open(field_json_path))
    rows = []
    for line in open(rows_md_path).read().splitlines():
        if line.lstrip().startswith("|") and not re.match(r"^\s*\|[\s:|-]+\|\s*$", line):
            rows.append(split_row(line))
    tables = [n for n in field.get("content", []) if n.get("type") == "table"]
    if not tables:
        raise SystemExit("no table found in the existing field, refusing to guess")
    target = tables[-1]
    header_cells = len(target["content"][0]["content"])
    body_rows = [r for r in target["content"][1:] if any(
        t.strip() for c in r["content"] for t in _cell_texts(c))]
    target["content"] = [target["content"][0]] + body_rows
    for row in rows:
        if len(row) != header_cells:
            raise SystemExit(f"row has {len(row)} cells, table header has {header_cells}: {row}")
        target["content"].append({"type": "tableRow", "content": [cell("tableCell", c) for c in row]})
    return field


def _cell_texts(cell_node):
    out = []
    def walk(n):
        if isinstance(n, dict):
            if n.get("type") == "text":
                out.append(n.get("text", ""))
            for c in n.get("content", []):
                walk(c)
    walk(cell_node)
    return out


if __name__ == "__main__":
    if len(sys.argv) >= 3 and sys.argv[1] == "render":
        json.dump(build_adf(open(sys.argv[2]).read()), sys.stdout)
    elif len(sys.argv) >= 4 and sys.argv[1] == "tables":
        json.dump(append_table_rows(sys.argv[2], sys.argv[3]), sys.stdout)
    else:
        sys.exit("usage: review2adf.py render <review.md> | tables <current-field.json> <rows.md>")
