
from LoG import *
from pathlib import Path
import orgparse
import re
import importer_common as ic

NAME="Import Org-Mode (Experimental)"
VERSION="0.1"
CATEGORY=SCtg.IMPORTER


def run(path):
    _import_org_file(path)

    refresh_panels()

    return True

_ORDERED_RE = re.compile(r"^( *)(\d+)[.)]\s")
_UNORDERED_RE = re.compile(r"^( *)([-+*])\s")
_TODO_RE = re.compile(r"^\[([ \-Xx])\]\s")
_QUOTE_RE = re.compile(r"^ *?#\+(BEGIN|END)_([A-Z]+)( (\w+))?")
_HRULE_RE = re.compile(r'^[ \t]*-{5,}[ \t]*$')
_TABLE_ROW_RE = re.compile(r'^[ \t]*\|')
_CAPTION_RE = re.compile(r'^[ \t]*#\+CAPTION:[ \t]*(.+)$', re.IGNORECASE)
_NAME_RE = re.compile(r'^[ \t]*#\+NAME:[ \t]*(.+)$', re.IGNORECASE)

# def _join_para_text(text: str) -> str:
#     text = re.sub(r"\s+", " ", text)
#     return text

# FORMATTING =======================================================================================
EMPH_FMT = {
    '*': VT.FMT.BOLD,
    '/': VT.FMT.ITALIC,
    '_': VT.FMT.UNDERLINE,
    '+': VT.FMT.STRIKETHRU,
}
LITERAL_FMT = {
    '~': VT.FMT.HIGHLIGHT,   # inline code
    '=': VT.FMT.FADED,       # verbatim
}

_ANY_EMPH_RE = re.compile(
    r'(?:(?<=^)|(?<=[ \t([{\'"-]))'
    r'([*/_+=~])'
    r'([^\s]|[^\s].*?[^\s])'
    r'\1'
    r'(?=[ \t.,:!?;\'")\]}-]|$)',
    re.MULTILINE
)

def _parse_emphasis(text):
    out = []
    spans = []
    pos = 0
    while True:
        m = _ANY_EMPH_RE.search(text, pos)
        if not m:
            out.append(text[pos:])
            break

        out.append(text[pos:m.start()])
        marker, inner = m.group(1), m.group(2)
        start = sum(len(s) for s in out)

        if marker in LITERAL_FMT:
            out.append(inner)
            spans.append((LITERAL_FMT[marker], start, start + len(inner)))
        else:
            inner_plain, inner_spans = _parse_emphasis(inner)
            for f, s, e in inner_spans:
                spans.append((f, s + start, e + start))
            out.append(inner_plain)
            spans.append((EMPH_FMT[marker], start, start + len(inner_plain)))

        pos = m.end()

    return ''.join(out), spans

# PARA LEVEL FORMATTING ============================================================================
def _infer_para_type(text):
    ps = None
    indent_level = 0
    quote_type = None
    F_quot_bgn = False

    if (m := _ORDERED_RE.match(text)):
        ps = VT.PLS.NUMBER
        print( "number ind level: ", text)
        text = text[m.end():].strip()
        indent_level = len(m.group(1)) // 2

    elif (m := _UNORDERED_RE.match(text) ):
        ps = VT.PLS.BULLET
        print( "bullet ind level: ", text)
        text = text[m.end():].strip()
        indent_level = len(m.group(1)) // 2

        # sub-style:
        m = _TODO_RE.match(text)
        if m:
            match m.group(1):
                case ' ':
                    ps = VT.PLS.TODO
                case '+':
                    ps = VT.PLS.PROGRESSED
                case 'x' | 'X':
                    ps = VT.PLS.DONE
            text = text[m.end():].strip()

    elif (m := _QUOTE_RE.match(text)):
        match m.group(2):
            case 'QUOTE':
                quote_type = VT.QT.LITERARY
                F_quot_bgn = m.group(1) == "BEGIN"
            case 'VERSE' | 'EXAMPLE':
                quote_type = VT.QT.GENERIC
                F_quot_bgn = m.group(1) == "BEGIN"
            case 'SRC':
                quote_type = ic.get_code_from_str(m.group(4)) if m.group(4) else VT.QT.GENERIC
                if quote_type:
                    F_quot_bgn = m.group(1) == "BEGIN"
        text = ""

    elif ( m := _TABLE_ROW_RE.match(text)):
        quote_type = VT.QT.GENERIC
        F_quot_bgn = True

    elif ( m := _CAPTION_RE.match(text)):
        text = m.group(1).strip()

    elif ( m := _NAME_RE.match(text)):
        text = ""

    return text, ps, indent_level, quote_type, F_quot_bgn

def _import_node(entry, para_last, node):
    # Add heading paragraph
    heading = entry.append_paragraph(node.heading)

    # Heading levels
    match node.level:
        case 1:
            heading.set_heading_level(VT.PHS.LARGE)
            heading.set_hrule(True)
        case 2:
            heading.set_heading_level(VT.PHS.LARGE)
        case 3:
            heading.set_heading_level(VT.PHS.MEDIUM)
        case 4 | 5:
            heading.add_format(VT.FMT.BOLD)

    para_last = heading

    # Add node body
    body = node.body

    para_text_cum = ""
    pls_last = None # para list style
    ind_lvl_last = 0
    qt_last = None # quotation type
    text_stripped = ""

    def _consume_text():
        nonlocal para_text_cum
        if para_text_cum:
            nonlocal para_last
            nonlocal pls_last
            nonlocal ind_lvl_last

            if qt_last is None or qt_last == VT.QT.LITERARY:
                para_text_cum = para_text_cum.strip()
            plain_text, spans = _parse_emphasis(para_text_cum)
            para_last = entry.append_paragraph(plain_text)

            # para level formatting:
            if pls_last:
                para_last.set_list_type(pls_last)
                para_last.set_indent_level(ind_lvl_last)
            if qt_last:
                para_last.set_quot_type(qt_last)

            # rich text formatting:
            for fmt, bgn, end in spans:
                para_last.add_format(fmt, bgn, end)

            para_text_cum = "" # cumulative text is consumed
        pls_last = pls
        ind_lvl_last = indent_level

    if body:
        for para_text in body.split("\n"):
            if not para_text.strip():
                pls = None # set new style to be set in _consume_text() to None
                indent_level = 0 # set ind lvl to be set in _consume_text() to 0
                _consume_text()
                continue

            if _HRULE_RE.match(para_text):
                pls = None # set new style to be set in _consume_text() to None
                indent_level = 0 # set ind lvl to be set in _consume_text() to 0
                _consume_text()
                para_last = entry.append_paragraph("")
                para_last.set_hrule(True)
                continue


            text_stripped, pls, indent_level, qt, F_qt_bgn = _infer_para_type(para_text)

            if pls != None or qt != None or (qt_last != None and qt_last != VT.QT.LITERARY):
                # flush buffer if current para is a list item or quote delimiter
                # or we are in a code block
                # TODO: add a special case for VERSE which is regarded like code but should be formatted like
                # literary quote
                _consume_text()
                if qt != None:
                    if F_qt_bgn and qt_last != None:
                        print("Nesting quotations is not supported!")
                    qt_last = qt if F_qt_bgn else None 
            if text_stripped:
                para_text_cum += " " + text_stripped

        # Remainder:
        _consume_text()

    # Process children recursively
    for child in node.children:
        para_last = _import_node(
            entry,
            para_last,
            child
        )

    return para_last

def _get_org_title(filepath):
    with open(filepath, encoding="utf-8") as f:
        for line in f:
            line = line.strip()

            if line.upper().startswith("#+TITLE:"):
                return line[8:].strip()

    return Path(filepath).stem

def _import_org_file(filepath):
    prev_para = None
    root = orgparse.load(filepath)
    title = _get_org_title(filepath)
    entry = Diary.get_main().create_entry(VT.ETS.NAME_ONLY, title )

    # Import all top-level headings
    for node in root.children:
        prev_para = _import_node(
            entry,
            prev_para,
            node
        )

