#!/usr/bin/env python3
"""
merge-lines-vietnamese.py — Nối dòng bị ngắt do word-wrap cho file text tiếng Việt

Công dụng:
  Khi trích xuất text từ PDF, các đoạn văn bị ngắt dòng lung tung.
  Script này nối các dòng trong cùng một đoạn văn, giữ nguyên:
    - Heading (##, ###)
    - Separator (---o0o--)
    - URL
    - Blank lines (paragraph breaks)

Nguyên tắc:
  - Chỉ GHÉP dòng khi dòng mới (line đang xét) bắt đầu bằng CHỮ THƯỜNG
    (sau khi loại bỏ ký tự điều khiển, số, ký tự đặc biệt ở đầu dòng).
  - Nếu dòng mới bắt đầu bằng CHỮ HOA (sau khi strip) → flush buffer trước.
  - Blank line: nếu dòng trước buffer không có dấu chấm cuối VÀ dòng kế tiếp
    (sau khi strip control chars) bắt đầu chữ thường → fake break (bỏ qua blank).
    Ngược lại → real paragraph break.
  - Dòng kết thúc bằng dấu chấm câu (.!?:;) → flush buffer ngay.
  - Heading / separator / URL → emit nguyên dòng, flush buffer trước.
  - Ký tự điều khiển (\x00-\x1f, \x7f-\x9f) ở đầu dòng được tự động loại bỏ
    trước khi xử lý.

Cách dùng:
  python3 merge-lines-vietnamese.py input.md [output.md]
  - Nếu không có output, ghi đè tại chỗ
"""

import re
import sys
from pathlib import Path


# ------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------

# Ký tự điều khiển (control characters)
CONTROL_CHARS = re.compile(r'[\x00-\x1f\x7f-\x9f]')

# Chữ cái tiếng Việt lowercase
LOWERCASE_LETTERS = 'a-zàảãáạằẳẵắặầẩẫấậèẻẽéẹềểễếệìỉĩíịòỏõóọồổỗốộờởỡớợùủũúụừửữứựỳỷỹýỵ'

# Chữ cái tiếng Việt uppercase
UPPERCASE_LETTERS = 'A-ZĂÂĐÊÔƠƯÀẢÃÁẠẰẲẴẮẶẦẨẪẤẬÈẺẼÉẸỀỂỄẾỆÌỈĨÍỊÒỎÕÓỌỒỔỖỐỘỜỞỠỚỢÙỦŨÚỤỪỬỮỨỰỲỶỸÝỴ'

# Các pattern phát hiện dòng metadata hoàn chỉnh (cần FLUSH, không merge)
# - Email: chứa @
# - URL: chứa http:// hoặc https:// (ngay cả khi không ở đầu dòng)
# - Date: pattern dd/dd/dddd hoặc dd-dd-dddd
_METADATA_PATTERNS = [
    re.compile(r'@'),                                  # email
    re.compile(r'https?://'),                          # URL ở bất kỳ đâu
    re.compile(r'\b\d{1,2}[-/]\d{1,2}[-/]\d{4}\b'),  # date: 02-11-2015
]


def strip_control(text: str) -> str:
    """Loại bỏ ký tự điều khiển khỏi text."""
    return CONTROL_CHARS.sub('', text)


def first_real_char(text: str) -> str | None:
    """
    Tìm ký tự chữ cái đầu tiên trong text, bỏ qua:
    - Ký tự điều khiển (\x00-\x1f, \x7f-\x9f)
    - Khoảng trắng
    - Số
    - Ký tự đặc biệt (dấu câu, ký hiệu...)
    
    Trả về ký tự đó hoặc None nếu không tìm thấy.
    """
    cleaned = strip_control(text).strip()
    for ch in cleaned:
        if ch.isalpha():
            return ch
    return None


def starts_with_lowercase(text: str) -> bool:
    """Kiểm tra text có bắt đầu bằng chữ thường không (sau strip control)."""
    ch = first_real_char(text)
    if ch is None:
        return False
    return ch.islower()


def starts_with_uppercase(text: str) -> bool:
    """Kiểm tra text có bắt đầu bằng chữ hoa không (sau strip control)."""
    ch = first_real_char(text)
    if ch is None:
        return False
    return ch.isupper()


def has_metadata_pattern(text: str) -> bool:
    """Kiểm tra dòng có chứa pattern metadata hoàn chỉnh (email, URL, date)."""
    for pattern in _METADATA_PATTERNS:
        if pattern.search(text):
            return True
    return False


def is_all_caps(text: str) -> bool:
    """
    Kiểm tra text có phải ALL CAPS (không có chữ thường) không.
    Chỉ kiểm tra 60 ký tự đầu để tránh noise từ nội dung phía sau.
    """
    chunk = text[:60].strip()
    has_upper = any(c.isupper() for c in chunk)
    has_lower = any(c.islower() for c in chunk)
    return has_upper and not has_lower


# ------------------------------------------------------------------
# Main logic
# ------------------------------------------------------------------

def merge_vietnamese_lines(text: str) -> str:
    """
    Nối các dòng bị ngắt trong đoạn văn tiếng Việt.
    Nguyên tắc chính: chỉ nối khi dòng mới bắt đầu bằng chữ thường
    (sau khi strip control characters).
    """
    SENTENCE_END = re.compile(r'[.!?:;]["\'\u201d]?$')

    lines = text.splitlines()
    result = []
    buffer = []

    for i, line in enumerate(lines):
        # Strip control chars trước (giữ nguyên khoảng trắng)
        line = strip_control(line)
        stripped = line.strip()

        # ----------------------------------------------------------------
        # 1. Blank line → xử lý paragraph break
        # ----------------------------------------------------------------
        if not stripped:
            if buffer:
                # Tìm dòng không trống kế tiếp
                next_non_blank = None
                for j in range(i + 1, len(lines)):
                    candidate = strip_control(lines[j]).strip()
                    if candidate:
                        next_non_blank = candidate
                        break

                if next_non_blank:
                    # Structural element: heading, separator → real break
                    if (next_non_blank.startswith('#')
                            or next_non_blank.startswith('---o0o--')
                            or next_non_blank == '---'):
                        result.append(' '.join(buffer))
                        buffer = []
                        result.append('')
                        continue

                    # Nếu next_non_blank là URL → skip nó, tìm text thật phía sau
                    # (URL thường là footer/header từ PDF, không phải structural break)
                    if next_non_blank.startswith(('http://', 'https://')):
                        # Tìm dòng text thật sau URL
                        real_next = None
                        for k in range(j + 1, len(lines)):
                            candidate = strip_control(lines[k]).strip()
                            if candidate:
                                real_next = candidate
                                break

                        if real_next:
                            # real_next là text thật → dùng nó cho fake break check
                            next_non_blank = real_next
                        else:
                            # Không có text thật → xử lý URL như real break
                            result.append(' '.join(buffer))
                            buffer = []
                            result.append('')
                            continue

                    # Fake break: dòng trước không dấu chấm câu
                    # VÀ dòng kế tiếp bắt đầu chữ thường (sau strip control)
                    if (not SENTENCE_END.search(buffer[-1])
                            and next_non_blank
                            and starts_with_lowercase(next_non_blank)):
                        continue  # Skip this blank line

                # Real break → flush buffer, emit blank
                result.append(' '.join(buffer))
                buffer = []
            result.append('')
            continue

        # ----------------------------------------------------------------
        # 2. Heading → flush buffer, emit nguyên dòng
        # ----------------------------------------------------------------
        if stripped.startswith('#'):
            if buffer:
                result.append(' '.join(buffer))
                buffer = []
            result.append(stripped)
            continue

        # ----------------------------------------------------------------
        # 3. Separator → flush buffer, emit nguyên dòng
        # ----------------------------------------------------------------
        if stripped.startswith('---o0o--'):
            if buffer:
                result.append(' '.join(buffer))
                buffer = []
            result.append(stripped)
            continue

        if stripped == '---':
            if buffer:
                result.append(' '.join(buffer))
                buffer = []
            result.append(stripped)
            continue

        # ----------------------------------------------------------------
        # 4. URL → emit nguyên dòng, KHÔNG flush buffer
        #    (URL thường là footer/header PDF, không phải structural break.
        #    Buffer được giữ nguyên để có thể merge với text phía sau URL.)
        # ----------------------------------------------------------------
        if stripped.startswith(('http://', 'https://')):
            result.append(stripped)
            continue

        # ----------------------------------------------------------------
        # 5. Regular text
        # ----------------------------------------------------------------
        if buffer and starts_with_uppercase(stripped):
            # Dòng mới bắt đầu chữ hoa — quyết định flush hay merge
            #
            # Mặc định MERGE (PDF word-wrap cắt ngang câu bất kỳ đâu).
            # Chỉ FLUSH khi có dấu hiệu rõ ràng của paragraph break.
            
            # 1. Dòng trước kết thúc câu (có dấu chấm .!?:;) → real break
            if SENTENCE_END.search(buffer[-1]):
                result.append(' '.join(buffer))
                buffer = []
                buffer.append(stripped)
                if SENTENCE_END.search(stripped):
                    result.append(' '.join(buffer))
                    buffer = []
                continue

            # 2. Dòng mới chứa ":" trong 50 ký tự đầu → metadata label
            if ':' in stripped[:50]:
                result.append(' '.join(buffer))
                buffer = []
                buffer.append(stripped)
                if SENTENCE_END.search(stripped):
                    result.append(' '.join(buffer))
                    buffer = []
                continue

            # 3. Dòng mới là ALL CAPS → section heading
            if is_all_caps(stripped):
                result.append(' '.join(buffer))
                buffer = []
                buffer.append(stripped)
                if SENTENCE_END.search(stripped):
                    result.append(' '.join(buffer))
                    buffer = []
                continue

            # 4. Dòng cuối buffer chứa metadata pattern (@, URL, date)
            if has_metadata_pattern(buffer[-1]):
                result.append(' '.join(buffer))
                buffer = []
                buffer.append(stripped)
                if SENTENCE_END.search(stripped):
                    result.append(' '.join(buffer))
                    buffer = []
                continue

            # 5. Dòng cuối buffer NGẮN (≤ 25 ký tự) → word-wrap fragment
            # (vd: "Chánh Kiến" (12) + "Và" → merge thành title)
            if len(buffer[-1].strip()) <= 25:
                pass  # Merge
            else:
                # Mọi trường hợp còn lại → continuation, merge
                pass

        # Dòng mới bắt đầu chữ thường, hoặc uppercase continuation, hoặc buffer rỗng
        # → nối tiếp (merge)
        buffer.append(stripped)

        # Nếu dòng kết thúc bằng dấu chấm câu → flush buffer ngay
        if SENTENCE_END.search(stripped):
            result.append(' '.join(buffer))
            buffer = []

    # Flush buffer cuối cùng
    if buffer:
        result.append(' '.join(buffer))

    return '\n'.join(result)


# ------------------------------------------------------------------
# CLI
# ------------------------------------------------------------------

def main():
    if len(sys.argv) < 2:
        print("Usage: python3 merge-lines-vietnamese.py input.md [output.md]", file=sys.stderr)
        sys.exit(1)

    input_path = Path(sys.argv[1])
    if not input_path.exists():
        print(f"Error: File not found: {input_path}", file=sys.stderr)
        sys.exit(1)

    output_path = Path(sys.argv[2]) if len(sys.argv) > 2 else input_path

    raw = input_path.read_text(encoding='utf-8')
    result = merge_vietnamese_lines(raw)
    output_path.write_text(result, encoding='utf-8')

    original_lines = len(raw.splitlines())
    result_lines = len(result.splitlines()) if result else 0
    print(f"✅ Xong! {input_path.name}")
    print(f"   {original_lines} dòng → {result_lines} dòng")
    print(f"   → {output_path}")


if __name__ == '__main__':
    main()
