# Copyright (C) 2007-2026 Ahmet Öztürk (aoz_2@yahoo.com)
#
# This file is part of Lifeograph.
#
# Lifeograph is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Lifeograph is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Lifeograph.  If not, see <http://www.gnu.org/licenses/>.

from LoG import *
from pathlib import Path
import re

NAME="Import Markdown"
VERSION="0.1"
CATEGORY=SCtg.IMPORTER

def run(path):
    entry = Diary.get_main().create_entry(VT.ETS.NAME_ONLY)
    entry.set_text(Path(path).stem)

    _import_markdown(path, entry)

    refresh_panels()

    return True

def _import_markdown(path, entry):
    _ORDERED_RE = re.compile(r"^(\d+)\.\s+")
    in_code_block = False
    code_lang = None

    CODE_LANG_MAP = {
        None: VT.QT.GENERIC,
        "": VT.QT.GENERIC,
        "text": VT.QT.GENERIC,
        "plain": VT.QT.GENERIC,

        "sh": VT.QT.BASH,
        "bash": VT.QT.BASH,

        "c": VT.QT.CPP,
        "cpp": VT.QT.CPP,
        "c++": VT.QT.CPP,

        "fortran": VT.QT.FORTRAN,

        "go": VT.QT.GO,
        "golang": VT.QT.GO,

        "haskell": VT.QT.HASKELL,
        "hs": VT.QT.HASKELL,

        "html": VT.QT.HTML,

        "java": VT.QT.JAVA,

        "js": VT.QT.JAVASCRIPT,
        "javascript": VT.QT.JAVASCRIPT,

        "kotlin": VT.QT.KOTLIN,
        "kt": VT.QT.KOTLIN,

        "lisp": VT.QT.LISP,

        "lua": VT.QT.LUA,

        "pascal": VT.QT.PASCAL,

        "perl": VT.QT.PERL,

        "py": VT.QT.PYTHON,
        "python": VT.QT.PYTHON,

        "ruby": VT.QT.RUBY,

        "rust": VT.QT.RUST,

        "scala": VT.QT.SCALA,

        "sql": VT.QT.SQL,
    }

    with open(path, encoding="utf-8") as f:
        for raw_line in f:
            line = raw_line.strip()
            if not line:
                continue

            # indentation
            indent_spaces = len(raw_line) - len(raw_line.lstrip(" "))
            indent_level = indent_spaces // 2

            # indented code block
            in_indented_code_block = False
            if raw_line.startswith("    ") and not in_code_block:
                in_indented_code_block = True
                code_lang = VT.QT.GENERIC
            # fenced code block
            elif line.startswith("```"):
                if not in_code_block:
                    lang = line[3:].strip().lower()
                    code_lang = CODE_LANG_MAP.get(lang, VT.QT.GENERIC)
                    in_code_block = True
                else:
                    in_code_block = False
                    code_lang = None
                continue

            if in_code_block or in_indented_code_block:
                p = entry.append_paragraph(line)
                p.set_quot_type(code_lang)
                continue

            # block quote
            quote_depth, line = _consume_block_quote_prefix(line)
            is_quote = quote_depth > 0
            if is_quote:
                indent_level += (quote_depth-1)

            # horizontal rule
            if _is_horizontal_rule(line):
                p = entry.append_paragraph("")
                p.set_hrule(True)
                #p.set_indent_level(indent_level) TODO: not supported in LoG
                continue

            # heading
            if line.startswith("#"):
                level = len(line) - len(line.lstrip("#"))
                text = line[level:].strip()
                p = entry.append_paragraph(text)
                match level:
                    case 1:
                        p.set_heading_level(VT.PHS.LARGE)
                        p.set_hrule(True)
                    case 2:
                        p.set_heading_level(VT.PHS.LARGE)
                    case 3:
                        p.set_heading_level(VT.PHS.MEDIUM)
                    case _:
                        p.add_format(VT.FMT.BOLD, 0, p.get_size())
                continue

            # TO-DO
            ps = None
            if line.startswith("- ["):
                mark = line[3]
                ps = {
                    " ": VT.PLS.TODO,
                    "-": VT.PLS.PROGRESSED,
                    "x": VT.PLS.DONE,
                    "~": VT.PLS.CANCELED,
                }.get(mark)
                line = line[6:].lstrip()

            # list
            LIST_MAP = {
                "- ": VT.PLS.BULLET,
                "+ ": VT.PLS.BULLET,
                "* ": VT.PLS.BULLET,
                "A. ": VT.PLS.LETTER,
                "a. ": VT.PLS.LETTER_SMALL,
                "I. ": VT.PLS.ROMAN,
                "i. ": VT.PLS.ROMAN_SMALL,
            }

            # ordered list
            m = _ORDERED_RE.match(line)
            if m:
                ps = VT.PLS.NUMBER
                line = line[m.end():]

            if ps is None:
                for k, v in LIST_MAP.items():
                    if line.startswith(k):
                        ps = v
                        line = line[len(k):]
                        break

            # paragraph
            p = entry.append_paragraph("")
            p.set_indent_level(indent_level)

            if is_quote:
                p.set_quote(True)
            if ps:
                p.set_list_type(ps)

            _parse_inline_formats(p, line)


# Inline parsing (overlap-safe, code-isolated)
def _parse_inline_formats(p, text):
    i = 0
    out = []
    stack = []

    def open_fmt(fmt):
        stack.append((fmt, len(out)))

    def close_fmt(fmt):
        for j in range(len(stack) - 1, -1, -1):
            if stack[j][0] == fmt:
                _, start = stack.pop(j)
                p.add_format(fmt, start, len(out))
                return

    while i < len(text):
        # link
        if text[i] == "[":
            label_start = len(out)
            i += 1
            while i < len(text) and text[i] != "]":
                out.append(text[i])
                i += 1
            label_end = len(out)

            if i + 1 < len(text) and text[i + 1] == "(":
                i += 2
                uri = ""
                while i < len(text) and text[i] != ")":
                    uri += text[i]
                    i += 1
                p.add_format(VT.FMT.LINK_URI, label_start, label_end).uri = uri
                i += 1
            continue

        # underline
        if text.startswith("<u>", i):
            open_fmt(VT.FMT.UNDERLINE)
            i += 3
            continue
        if text.startswith("</u>", i):
            close_fmt(VT.FMT.UNDERLINE)
            i += 4
            continue

        # bold / italic / strikethru / highlight
        TOKENS = [
            ("**", VT.FMT.BOLD),
            ("__", VT.FMT.BOLD),
            ("~~", VT.FMT.STRIKETHRU),
            ("==", VT.FMT.HIGHLIGHT),
            ("`", VT.FMT.ITALIC), # no actual support for inline code
            ("*", VT.FMT.ITALIC),
            ("_", VT.FMT.ITALIC),
        ]

        matched = False
        for tok, fmt in TOKENS:
            if not text.startswith(tok, i):
                continue

            if stack and stack[-1][0] == fmt:
                if tok.startswith("_"):
                    if _is_boundary_alnum_after(text, i, len(tok)):
                        continue
                close_fmt(fmt)
            else:
                if tok.startswith("_"):
                    if _is_boundary_alnum_before(text, i):
                        continue
                open_fmt(fmt)

            i += len(tok)
            matched = True
            break

        if matched:
            continue

        out.append(text[i])
        i += 1

    p.set_text("".join(out))

def _is_horizontal_rule(line: str) -> bool:
    s = line.strip()
    if len(s) < 3:
        return False
    return all(c == s[0] for c in s) and s[0] in "-*_"

def _is_boundary_alnum_before(text, i):
    before = text[i - 1] if i > 0 else " "
    return before.isalnum()

def _is_boundary_alnum_after(text, i, length):
    after = text[i + length] if i + length < len(text) else " "
    return after.isalnum()

def _consume_block_quote_prefix(line: str):
    # returns (depth, rest_of_line)
    # supports: >text, > text, >    > text, >> text, etc.
    i = 0
    depth = 0
    n = len(line)

    while i < n:
        if line[i] == ">":
            depth += 1
            i += 1
            # skip optional spaces after each '>'
            while i < n and line[i] == " ":
                i += 1
        else:
            break

    return depth, line[i:]
