#!/usr/bin/env python
# md2pdf.py
# Usage:
#   python md2pdf.py input.md output.pdf
#   python md2pdf.py input.md output.pdf --font "C:\Windows\Fonts\msgothic.ttc"

import sys
import argparse
import re
from pathlib import Path

import markdown
from fpdf import FPDF, XPos, YPos

# ---------------- util ----------------

def insert_soft_breaks(s: str, hard_chunk=60) -> str:
    """
    改行できない長大トークン（URL等）にゼロ幅スペースを挿入して折り返しを許可。
    区切り記号の後に挿入 + それでも長い連続文字列は hard_chunk ごとに挿入。
    """
    # 区切り記号の後にゼロ幅スペースを入れる
    s = re.sub(r'([/_\-.&?=#:])', r'\1\u200b', s)
    # 空白のない長大連続部分を強制分割
    def break_long_token(m):
        token = m.group(0)
        out = []
        for i in range(0, len(token), hard_chunk):
            out.append(token[i:i+hard_chunk])
        return '\u200b'.join(out)
    s = re.sub(r'[^\s\u200b]{' + str(hard_chunk*2) + r',}', break_long_token, s)
    return s

def md_to_html(md_text: str) -> str:
    # fenced_code / tables などを有効化
    return markdown.markdown(md_text, extensions=["extra", "fenced_code", "tables"])

def extract_code_blocks(html: str):
    """
    <pre><code>...</code></pre> を先に抜き出し、プレースホルダに置換。
    返り値: (置換後html, [code_strings])
    """
    code_list = []
    def repl(m):
        code = m.group(1)
        code_list.append(code)
        return f"@@@CODEBLOCK_{len(code_list)-1}@@@"
    # DOTALL で複数行
    html2 = re.sub(r"<pre><code>(.*?)</code></pre>", repl, html, flags=re.IGNORECASE|re.DOTALL)
    return html2, code_list

# ---------------- PDF ----------------

class PDF(FPDF):
    def header(self): pass
    def footer(self): pass

    def _body_width(self) -> float:
        return self.w - self.l_margin - self.r_margin

    def h1(self, text: str):
        self.set_font('DOCFONT', '', 18)
        self.ln(2)
        self.multi_cell(self._body_width(), 10, text, new_x=XPos.LMARGIN, new_y=YPos.NEXT)
        self.ln(1)

    def h2(self, text: str):
        self.set_font('DOCFONT', '', 16)
        self.ln(1)
        self.multi_cell(self._body_width(), 9, text, new_x=XPos.LMARGIN, new_y=YPos.NEXT)

    def para(self, text: str):
        self.set_font('DOCFONT', '', 12)
        try:
            self.multi_cell(self._body_width(), 7, text)
        except Exception:
            self.multi_cell(self._body_width(), 7, insert_soft_breaks(text))
        self.ln(0.5)

    def bullet(self, text: str):
        self.set_font('DOCFONT', '', 12)
        bullet_w = 6
        remain = self._body_width() - bullet_w
        if remain < 20:
            # 残幅が異常に小さい場合は段落として描画
            return self.para(f"• {text}")
        self.cell(bullet_w, 7, "• ")
        try:
            self.multi_cell(remain, 7, text)
        except Exception:
            self.multi_cell(remain, 7, insert_soft_breaks(text))

    def code_block(self, code: str):
        # 等幅 + グレー背景
        self.set_font('DOCFONT', '', 11)
        # 背景用に四角を描く（簡易）：テキスト高さを行数で見積もる
        lines = code.replace('\t', '    ').splitlines() or [""]
        h = max(7 * len(lines) + 6, 14)
        x0, y0 = self.get_x(), self.get_y()
        w = self._body_width()
        # 背景
        self.set_fill_color(245, 245, 245)
        self.rect(x0, y0, w, h, style="F")
        # 枠線（薄め）
        self.set_draw_color(200, 200, 200)
        self.rect(x0, y0, w, h)
        # テキスト
        self.set_xy(x0 + 2, y0 + 3)
        for i, ln in enumerate(lines):
            # ソフトブレークで安全に
            ln2 = insert_soft_breaks(ln, hard_chunk=80)
            try:
                self.multi_cell(w - 4, 6, ln2)
            except Exception:
                self.multi_cell(w - 4, 6, ln2)
        # 次の行へ
        self.ln(3)

# ---------------- HTML -> PDF ----------------

def html_to_pdf(html: str, output_filename: str, font_path: str | None):
    # 先にコードブロックを抜き出し
    html, code_list = extract_code_blocks(html)

    pdf = PDF(format="A4")
    pdf.set_auto_page_break(auto=True, margin=15)
    pdf.add_page()

    # 日本語フォントを登録
    candidates = []
    if font_path:
        candidates.append(Path(font_path))
    candidates.append(Path(r"C:\Windows\Fonts\msgothic.ttc"))  # Windows
    candidates += [
        Path("/System/Library/Fonts/ヒラギノ角ゴシック W4.ttc"),
        Path("/Library/Fonts/Arial Unicode.ttf"),
        Path("/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc"),
        Path("/usr/share/fonts/truetype/noto/NotoSansCJKjp-Regular.otf"),
    ]
    chosen = None
    for p in candidates:
        if p.is_file():
            chosen = str(p)
            break
    if not chosen:
        raise FileNotFoundError("日本語フォントが見つかりません。--font で TTF/OTF/TTC を指定してください。")

    # fpdf2 v2.5.1 以降は uni は不要・非推奨
    pdf.add_font('DOCFONT', '', chosen)

    # シンプルな行処理（見出し/段落/リスト）
    lines = html.splitlines()
    in_ul = False

    for raw in lines:
        s = raw.strip()
        if not s:
            continue

        # 置換されたコードブロック
        m = re.match(r"^@@@CODEBLOCK_(\d+)@@@$", s)
        if m:
            idx = int(m.group(1))
            code = code_list[idx]
            pdf.code_block(code)
            continue

        # <ul> / </ul>
        if re.match(r"^<ul[^>]*>\s*$", s, re.IGNORECASE):
            in_ul = True
            continue
        if re.match(r"^</ul>\s*$", s, re.IGNORECASE):
            in_ul = False
            continue

        # <h1> / <h2>
        m = re.match(r"^<h1[^>]*>(.*?)</h1>\s*$", s, re.IGNORECASE)
        if m:
            pdf.h1(re.sub(r"<[^>]+>", "", m.group(1)))
            continue
        m = re.match(r"^<h2[^>]*>(.*?)</h2>\s*$", s, re.IGNORECASE)
        if m:
            pdf.h2(re.sub(r"<[^>]+>", "", m.group(1)))
            continue

        # <li>
        m = re.match(r"^<li[^>]*>(.*?)</li>\s*$", s, re.IGNORECASE)
        if m:
            text = re.sub(r"<[^>]+>", "", m.group(1))
            pdf.bullet(text)
            continue

        # <p>
        m = re.match(r"^<p[^>]*>(.*?)</p>\s*$", s, re.IGNORECASE)
        if m:
            text = re.sub(r"<[^>]+>", "", m.group(1))
            pdf.para(text)
            continue

        # その他はタグ剥がして段落
        pdf.para(re.sub(r"<[^>]+>", "", s))

    pdf.output(output_filename)

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

def main():
    parser = argparse.ArgumentParser(description="Convert Markdown to PDF (simple, Japanese-capable).")
    parser.add_argument("input", help="input markdown file path")
    parser.add_argument("output", help="output pdf file path")
    parser.add_argument("--font", help="path to a TTF/OTF/TTC font for Japanese", default=None)
    args = parser.parse_args()

    md_path = Path(args.input)
    if not md_path.is_file():
        print(f"Input file not found: {md_path}", file=sys.stderr)
        sys.exit(1)

    md_text = md_path.read_text(encoding="utf-8")
    html = md_to_html(md_text)
    html_to_pdf(html, args.output, args.font)
    print(f"Saved: {args.output}")

if __name__ == "__main__":
    main()
