#!/usr/bin/env python3
"""
指定されたディレクトリ以下のHTMLファイルを走査し、サイトマップ（HTML形式）を生成するスクリプト。

このスクリプトは、ルートディレクトリから再帰的にHTMLファイルを検索し、
各ファイルの<title>タグからタイトルを抽出して、それらをリンクとして含むHTMLサイトマップを作成します。
除外パターンを指定することで、特定のディレクトリやファイルをサイトマップから除外できます。

:doc:`make_sitemap_from_path_usage`
"""
import os
import argparse
from pathlib import Path
import re

def find_html_files(root: Path, exclude_patterns=None):
    """指定されたルートディレクトリ以下からHTMLファイルを再帰的に探索し、除外パターンに一致するものを除外します。

    ディレクトリ名やファイルパスが `exclude_patterns` のいずれかの正規表現に一致する場合、
    そのディレクトリの内容やファイルはサイトマップに含められません。

    :param root: 検索を開始するルートディレクトリのパス。
    :type root: Path
    :param exclude_patterns: 除外する正規表現パターンのリスト。
                             ディレクトリパスまたはファイルパスがこれらのパターンに一致する場合、
                             そのファイルはサイトマップから除外されます。
                             指定しない場合は除外されません。
    :type exclude_patterns: list[str] | None
    :returns: 見つかったHTMLファイルのPathオブジェクトのリスト。
    :rtype: list[Path]
    """
    html_files = []
    compiled_patterns = [re.compile(p) for p in (exclude_patterns or [])]

    for dirpath, _, filenames in os.walk(root):
        # 除外ディレクトリ判定
        if any(p.search(dirpath) for p in compiled_patterns):
            continue
        for filename in filenames:
            if filename.lower().endswith(".html"):
                full_path = Path(dirpath) / filename
                # ファイルパスも除外判定
                if any(p.search(str(full_path)) for p in compiled_patterns):
                    continue
                html_files.append(full_path)
    return html_files

def extract_title(file_path: Path):
    """指定されたHTMLファイルから<title>タグの内容を抽出します。

    ファイルが読み取れない場合や<title>タグが見つからない場合は、ファイル名をタイトルとして返します。
    HTMLコンテンツの読み込み時にエンコーディングエラーが発生しても処理を継続します。

    :param file_path: タイトルを抽出するHTMLファイルのパス。
    :type file_path: Path
    :returns: 抽出されたタイトル文字列、またはファイル名。
    :rtype: str
    """
    try:
        with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
            content = f.read()
        match = re.search(r"<title>(.*?)</title>", content, re.IGNORECASE | re.DOTALL)
        if match:
            return match.group(1).strip()
    except Exception:
        pass
    return file_path.name  # fallback: ファイル名

def generate_sitemap(files, root: Path):
    """HTMLサイトマップのコンテンツを生成します。

    見つかったHTMLファイルのリストを基に、HTML形式のサイトマップ文字列を作成します。
    ファイルはパスでソートされ、各エントリにはタイトルとルートからの相対パスが含まれます。

    :param files: サイトマップに含めるHTMLファイルのPathオブジェクトのリスト。
    :type files: list[Path]
    :param root: 相対パスを計算するためのルートディレクトリのパス。
    :type root: Path
    :returns: 生成されたHTMLサイトマップの文字列。
    :rtype: str
    """
    lines = [
        "<!DOCTYPE html>",
        "<html>",
        "<head><meta charset='utf-8'><title>Site Map</title></head>",
        "<body>",
        "<h1>Site Map</h1>",
        "<ul>"
    ]
    for f in sorted(files):
        rel_path = f.relative_to(root).as_posix()  # separator を '/' に統一
        title = extract_title(f)
        lines.append(f"<li><a href='{rel_path}'>{title}</a> ({rel_path})</li>")
    lines.extend(["</ul>", "</body>", "</html>"])
    return "\n".join(lines)

def main():
    """スクリプトのエントリポイントです。

    コマンドライン引数を解析し、指定されたディレクトリからHTMLファイルを検索して、
    サイトマップを生成し、指定されたファイルに出力します。
    実行結果の要約をコンソールに表示します。
    """
    parser = argparse.ArgumentParser(description="Generate sitemap from HTML files.")
    parser.add_argument("root", nargs="?", default=".", help="検索 root ディレクトリ (デフォルト: .)")
    parser.add_argument("output", nargs="?", default="sitemap.html", help="出力ファイル名 (デフォルト: sitemap.html)")
    parser.add_argument("--exclude", nargs="*", default=[r"_vti_cnf"], help="除外する正規表現パターン (例: --exclude _vti_cnf tmp)")
    args = parser.parse_args()

    root = Path(args.root).resolve()
    files = find_html_files(root, args.exclude)
    sitemap = generate_sitemap(files, root)

    with open(args.output, "w", encoding="utf-8") as f:
        f.write(sitemap)

    print(f"Sitemap generated: {args.output}")
    print(f"Found {len(files)} HTML files under {root} (excluded patterns: {args.exclude})")

if __name__ == "__main__":
    main()