#!/usr/bin/env python3
"""
Analyze fetched WeChat articles and generate style templates.

Reads HTML articles from a directory, extracts CSS style patterns,
and generates template config files for md_to_wechat.py.

Usage:
    python analyze_styles.py ./articles/ --output ./templates/
    python analyze_styles.py ./articles/ --output ./templates/ --verbose
"""

import argparse
import json
import re
import sys
from pathlib import Path
from collections import Counter, defaultdict
from datetime import datetime


# ============================================================
# Style Extractor
# ============================================================

class StyleExtractor:
    """Extract and aggregate CSS styles from WeChat article HTML."""

    def __init__(self):
        self.styles = {
            "text_colors": Counter(),
            "bg_colors": Counter(),
            "border_colors": Counter(),
            "font_sizes": Counter(),
            "font_weights": Counter(),
            "line_heights": Counter(),
            "letter_spacings": Counter(),
            "margins": Counter(),
            "paddings": Counter(),
            "border_radii": Counter(),
            "text_aligns": Counter(),
        }
        self.tag_styles = defaultdict(lambda: {
            "colors": Counter(),
            "sizes": Counter(),
            "margins": Counter(),
            "paddings": Counter(),
            "bg_colors": Counter(),
        })
        self.section_headers = []
        self.numbered_items = []
        self.special_elements = {
            "has_article_number": 0,
            "has_author_line": 0,
            "has_end_marker": 0,
            "has_cta_block": 0,
            "has_contact_info": 0,
            "has_part_header": 0,
            "has_preface": 0,
            "has_conclusion": 0,
        }

    def extract_from_html(self, html, title=""):
        """Extract styles from a single article's HTML."""
        # Extract all inline style attributes
        style_matches = re.findall(r'style="([^"]*)"', html)

        for style_str in style_matches:
            self._parse_style_string(style_str, html)

        # Detect section headers (PART.01, etc.)
        part_matches = re.findall(
            r'<[^>]*>(PART\.?\s*\d+[^<]*)</[^>]*>', html, re.IGNORECASE
        )
        self.section_headers.extend(part_matches)

        # Detect numbered items (01, 02 in standalone elements)
        number_matches = re.findall(
            r'<[^>]*>\s*(\d{2})\s*</[^>]*>', html
        )
        self.numbered_items.extend(number_matches)

        # Detect special elements
        if "陪伴你的第" in html:
            self.special_elements["has_article_number"] += 1
        if "作者" in html and ("▌" in html or "▏" in html):
            self.special_elements["has_author_line"] += 1
        if "·END·" in html or "END" in html:
            self.special_elements["has_end_marker"] += 1
        if "贴身定制方案" in html or "如果您所在的企业" in html:
            self.special_elements["has_cta_block"] += 1
        if any(kw in html for kw in ["www.tiny-wings", "TinyWings@"]):
            self.special_elements["has_contact_info"] += 1
        if any(re.search(r'PART\.?\s*\d+', html, re.IGNORECASE) for _ in [1]):
            self.special_elements["has_part_header"] += 1
        if "前言" in html:
            self.special_elements["has_preface"] += 1
        if "结语" in html:
            self.special_elements["has_conclusion"] += 1

    def _parse_style_string(self, style_str, full_html=""):
        """Parse a single style attribute string."""
        declarations = style_str.split(";")

        for decl in declarations:
            decl = decl.strip()
            if not decl:
                continue

            # color: #xxx or rgb(r,g,b) or rgba(r,g,b,a)
            m = re.match(r'color:\s*(#[0-9a-fA-F]{3,8})', decl)
            if m:
                self.styles["text_colors"][m.group(1).lower()] += 1
            else:
                m = re.match(r'color:\s*rgba?\(([^)]+)\)', decl)
                if m:
                    self.styles["text_colors"][self._rgb_to_hex(m.group(1))] += 1

            # background: #xxx or rgb(r,g,b) or rgba(r,g,b,a)
            m = re.match(r'background(?:-color)?:\s*(#[0-9a-fA-F]{3,8})', decl)
            if m:
                self.styles["bg_colors"][m.group(1).lower()] += 1
            else:
                m = re.match(r'background(?:-color)?:\s*rgba?\(([^)]+)\)', decl)
                if m:
                    self.styles["bg_colors"][self._rgb_to_hex(m.group(1))] += 1

            # border: ... #xxx or rgb
            m = re.search(r'border[^:]*:\s*\d+px\s+\w+\s+(#[0-9a-fA-F]{3,8})', decl)
            if m:
                self.styles["border_colors"][m.group(1).lower()] += 1
            else:
                m = re.search(r'border[^:]*:\s*\d+px\s+\w+\s+rgba?\(([^)]+)\)', decl)
                if m:
                    self.styles["border_colors"][self._rgb_to_hex(m.group(1))] += 1

            # font-size: Npx
            m = re.match(r'font-size:\s*(\d+)px', decl)
            if m:
                self.styles["font_sizes"][m.group(1)] += 1

            # font-weight: xxx
            m = re.match(r'font-weight:\s*(\w+)', decl)
            if m:
                self.styles["font_weights"][m.group(1)] += 1

            # line-height: N.N
            m = re.match(r'line-height:\s*([\d.]+)', decl)
            if m:
                self.styles["line_heights"][m.group(1)] += 1

            # letter-spacing: Npx
            m = re.match(r'letter-spacing:\s*([\d.]+)px', decl)
            if m:
                self.styles["letter_spacings"][m.group(1)] += 1

            # margin: ...
            m = re.match(r'margin:\s*([^;]+)', decl)
            if m:
                self.styles["margins"][m.group(1).strip()] += 1

            # padding: ...
            m = re.match(r'padding:\s*([^;]+)', decl)
            if m:
                self.styles["paddings"][m.group(1).strip()] += 1

            # border-radius: Npx
            m = re.match(r'border-radius:\s*(\d+)px', decl)
            if m:
                self.styles["border_radii"][m.group(1)] += 1

            # text-align: xxx
            m = re.match(r'text-align:\s*(\w+)', decl)
            if m:
                self.styles["text_aligns"][m.group(1)] += 1

    @staticmethod
    def _rgb_to_hex(rgb_str):
        """Convert rgb(r,g,b) or rgba(r,g,b,a) string to hex color."""
        parts = [p.strip() for p in rgb_str.split(",")]
        try:
            r, g, b = int(parts[0]), int(parts[1]), int(parts[2])
            return f"#{r:02x}{g:02x}{b:02x}"
        except (ValueError, IndexError):
            return rgb_str.strip()

    def get_top(self, counter, n=5):
        """Get top N items from a Counter."""
        return [{"value": k, "count": v} for k, v in counter.most_common(n)]

    def generate_profile(self, total_articles):
        """Generate a comprehensive style profile."""
        return {
            "analysis_date": datetime.now().strftime("%Y-%m-%d %H:%M"),
            "total_articles_analyzed": total_articles,
            "text_colors": self.get_top(self.styles["text_colors"], 10),
            "bg_colors": self.get_top(self.styles["bg_colors"], 10),
            "border_colors": self.get_top(self.styles["border_colors"], 10),
            "font_sizes": self.get_top(self.styles["font_sizes"], 8),
            "font_weights": self.get_top(self.styles["font_weights"], 5),
            "line_heights": self.get_top(self.styles["line_heights"], 5),
            "letter_spacings": self.get_top(self.styles["letter_spacings"], 5),
            "border_radii": self.get_top(self.styles["border_radii"], 5),
            "text_aligns": self.get_top(self.styles["text_aligns"], 5),
            "special_elements": self.special_elements,
            "section_headers_sample": self.section_headers[:10],
            "numbered_items_sample": self.numbered_items[:10],
        }


# ============================================================
# Template Generator
# ============================================================

def generate_theme_from_profile(profile, theme_name="analyzed"):
    """Generate a theme config for md_to_wechat.py based on style profile.

    Takes the most common colors and sizes from the analysis and
    creates a theme dict that matches the THEMES format in md_to_wechat.py.
    """
    # Pick the most common dark color as primary (skip black/gray defaults)
    text_colors = profile.get("text_colors", [])
    bg_colors = profile.get("bg_colors", [])
    border_colors = profile.get("border_colors", [])

    # Filter out pure black/white/gray for primary color selection
    def is_brand_color(hex_color):
        hex_color = hex_color.lstrip("#")
        if len(hex_color) == 3:
            hex_color = "".join(c * 2 for c in hex_color)
        r, g, b = int(hex_color[:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
        # Skip pure black, white, and grays
        if r == g == b:
            return False
        # Skip very light colors
        if r > 240 and g > 240 and b > 240:
            return False
        return True

    brand_text_colors = [c for c in text_colors if is_brand_color(c["value"])]
    brand_bg_colors = [c for c in bg_colors if is_brand_color(c["value"])]
    brand_border_colors = [c for c in border_colors if is_brand_color(c["value"])]

    # Primary color: most common brand text color, or fallback
    primary = brand_text_colors[0]["value"] if brand_text_colors else "#2B5797"
    secondary = brand_text_colors[1]["value"] if len(brand_text_colors) > 1 else "#378ADD"

    # Background accent: most common light brand bg color
    accent = brand_bg_colors[0]["value"] if brand_bg_colors else "#E6F1FB"

    # Font sizes
    font_sizes = {item["value"]: item["count"] for item in profile.get("font_sizes", [])}
    body_size = max(font_sizes, key=font_sizes.get) if font_sizes else "15"
    title_size = str(max(int(s) for s in font_sizes if int(s) >= 18)) if any(int(s) >= 18 for s in font_sizes) else "22"

    # Line height
    line_heights = {item["value"]: item["count"] for item in profile.get("line_heights", [])}
    body_line_height = max(line_heights, key=line_heights.get) if line_heights else "1.8"

    return {
        "name": f"分析生成-{theme_name}",
        "primary": primary,
        "secondary": secondary,
        "accent": accent,
        "section_bg": accent,
        "number_color": primary,
        "border_color": primary,
        "text_color": "#3F3F3F",
        "heading_color": "#1A1A1A",
        "quote_bg": accent,
        "quote_border": primary,
        "cta_bg": accent,
        "end_color": "#999999",
        "link_color": primary,
        "_meta": {
            "body_font_size": f"{body_size}px",
            "title_font_size": f"{title_size}px",
            "body_line_height": body_line_height,
            "analyzed_from": profile.get("total_articles_analyzed", 0),
        }
    }


# ============================================================
# Main CLI
# ============================================================

def main():
    parser = argparse.ArgumentParser(
        description="Analyze WeChat article styles and generate templates"
    )
    parser.add_argument("input", help="Directory containing fetched article HTML files")
    parser.add_argument("--output", "-o", default="./templates/",
                        help="Output directory for style templates")
    parser.add_argument("--verbose", "-v", action="store_true",
                        help="Print detailed analysis")

    args = parser.parse_args()

    input_dir = Path(args.input)
    output_dir = Path(args.output)
    output_dir.mkdir(parents=True, exist_ok=True)

    # Find all HTML files
    html_files = sorted(input_dir.glob("*.html"))
    if not html_files:
        print(f"Error: No HTML files found in {input_dir}", file=sys.stderr)
        sys.exit(1)

    print(f"=== Analyzing {len(html_files)} article HTML files ===")

    # Overall extractor
    overall_extractor = StyleExtractor()

    # Per-type extractors
    type_extractors = defaultdict(StyleExtractor)
    type_counts = Counter()

    for html_file in html_files:
        html = html_file.read_text(encoding="utf-8")

        # Determine article type from filename
        # Format: 001_tech.html, 002_culture.html, etc.
        parts = html_file.stem.split("_")
        article_type = parts[1] if len(parts) >= 2 else "default"

        # Overall analysis
        overall_extractor.extract_from_html(html)

        # Per-type analysis
        type_extractors[article_type].extract_from_html(html)
        type_counts[article_type] += 1

    # Generate overall profile
    overall_profile = overall_extractor.generate_profile(len(html_files))

    # Save overall profile
    profile_file = output_dir / "style_profile.json"
    with open(profile_file, "w", encoding="utf-8") as f:
        json.dump(overall_profile, f, ensure_ascii=False, indent=2)
    print(f"\nOverall style profile saved to: {profile_file}")

    # Print summary
    print(f"\n--- Analysis Summary ---")
    print(f"Total articles: {len(html_files)}")
    print(f"Article types: {dict(type_counts)}")

    print(f"\nTop text colors:")
    for item in overall_profile["text_colors"][:8]:
        print(f"  {item['value']}: {item['count']} times")

    print(f"\nTop background colors:")
    for item in overall_profile["bg_colors"][:8]:
        print(f"  {item['value']}: {item['count']} times")

    print(f"\nTop font sizes:")
    for item in overall_profile["font_sizes"][:6]:
        print(f"  {item['value']}px: {item['count']} times")

    print(f"\nTop line heights:")
    for item in overall_profile["line_heights"][:5]:
        print(f"  {item['value']}: {item['count']} times")

    print(f"\nSpecial elements detected:")
    for element, count in overall_profile["special_elements"].items():
        pct = (count / len(html_files)) * 100
        print(f"  {element}: {count}/{len(html_files)} ({pct:.0f}%)")

    # Generate per-type themes
    print(f"\n--- Generating Theme Templates ---")
    themes = {}

    for atype, extractor in type_extractors.items():
        if type_counts[atype] < 1:
            continue

        profile = extractor.generate_profile(type_counts[atype])
        theme = generate_theme_from_profile(profile, theme_name=atype)
        themes[atype] = theme

        print(f"\n  [{atype}] ({type_counts[atype]} articles)")
        print(f"    Primary: {theme['primary']}")
        print(f"    Secondary: {theme['secondary']}")
        print(f"    Accent: {theme['accent']}")
        print(f"    Body font: {theme['_meta']['body_font_size']}")
        print(f"    Line height: {theme['_meta']['body_line_height']}")

    # If only one dominant type, also create it as "default"
    if len(themes) == 1:
        only_type = list(themes.keys())[0]
        themes["default"] = themes[only_type].copy()
        themes["default"]["name"] = "分析生成-默认"
        print(f"\n  [default] (copied from {only_type})")

    # If multiple types but no "default", create one from overall
    if "default" not in themes and len(themes) > 1:
        themes["default"] = generate_theme_from_profile(overall_profile, "default")
        print(f"\n  [default] (from overall analysis)")

    # Save themes as JSON (for use by md_to_wechat.py)
    themes_file = output_dir / "themes.json"
    # Remove _meta from saved themes for cleaner output
    clean_themes = {}
    for k, v in themes.items():
        clean_themes[k] = {key: val for key, val in v.items() if not key.startswith("_")}

    with open(themes_file, "w", encoding="utf-8") as f:
        json.dump(clean_themes, f, ensure_ascii=False, indent=2)
    print(f"\nThemes saved to: {themes_file}")

    # Generate Python config file for md_to_wechat.py
    py_config = output_dir / "analyzed_themes.py"
    with open(py_config, "w", encoding="utf-8") as f:
        f.write('"""\nAuto-generated theme configurations from article style analysis.\n')
        f.write(f'Generated: {datetime.now().strftime("%Y-%m-%d %H:%M")}\n')
        f.write(f'Source articles: {len(html_files)}\n')
        f.write('"""\n\n')
        f.write("ANALYZED_THEMES = ")
        f.write(json.dumps(clean_themes, ensure_ascii=False, indent=2))
        f.write("\n")
    print(f"Python config saved to: {py_config}")

    # Verbose output
    if args.verbose:
        print(f"\n--- Verbose: Per-Type Details ---")
        for atype, extractor in type_extractors.items():
            profile = extractor.generate_profile(type_counts[atype])
            print(f"\n[{atype}] ({type_counts[atype]} articles)")
            print(f"  Text colors: {profile['text_colors'][:3]}")
            print(f"  BG colors: {profile['bg_colors'][:3]}")
            print(f"  Font sizes: {profile['font_sizes'][:3]}")

    print(f"\n=== Done! Templates saved to {output_dir} ===")
    print(f"\nNext steps:")
    print(f"  1. Review the generated themes in {themes_file}")
    print(f"  2. Copy desired themes to md_to_wechat.py THEMES dict")
    print(f"  3. Test with: python md_to_wechat.py article.md --theme <type>")


if __name__ == "__main__":
    main()
