"""Extract the official GAUDELOT PDF catalog into site data and product thumbnails."""

from __future__ import annotations

import json
import re
from pathlib import Path

import pdfplumber
from PIL import Image, ImageChops


ROOT = Path(__file__).resolve().parents[1]
PDF_PATH = ROOT.parent / "公司资料" / "公司产品" / "汕头市高德乐产品型号表20260630.pdf"
RENDER_DIR = ROOT / "tmp" / "pdfs" / "catalog-hi"
IMAGE_DIR = ROOT / "public" / "catalog-products"
DATA_PATH = ROOT / "app" / "catalog-data.ts"

CATEGORY_BY_PAGE = {
    **{page: "标准舵机" for page in range(1, 14)},
    **{page: "异形舵机" for page in range(14, 16)},
    **{page: "全金属舵机" for page in range(16, 20)},
    **{page: "大扭矩舵机" for page in range(20, 22)},
    **{page: "积木舵机" for page in range(22, 24)},
    **{page: "标准齿轮箱" for page in range(24, 28)},
    **{page: "翻盖电机" for page in range(28, 30)},
}

# Business categories can intentionally differ from the source page heading.
CATEGORY_BY_CODE = {
    "4.A.05.A0008": "异形舵机",
}

SPEC_LABELS = {
    "标准舵机": [
        "产品尺寸", "最大砝码负载", "堵转扭矩", "空载速度", "可操作角度",
        "额定电压", "电压范围", "外壳材质", "齿轮级数", "齿轮材质", "马达类型", "端子连接线",
    ],
    "异形舵机": [
        "产品尺寸", "最大砝码负载", "堵转扭矩", "空载速度", "可操作角度",
        "额定电压", "电压范围", "外壳材质", "齿轮级数", "齿轮材质", "马达类型", "端子连接线",
    ],
    "全金属舵机": [
        "产品尺寸", "马达类型", "传感器类型", "信号类型", "协议版本", "连接线类型",
        "电压范围", "额定电压", "操作角度范围", "外壳材质", "齿轮材质", "齿轮比",
        "齿轮级数", "堵转扭矩", "负载扭矩", "额定扭矩", "转动速度", "转动速度RPM",
    ],
    "大扭矩舵机": [
        "产品尺寸", "马达类型", "传感器类型", "信号类型", "协议版本", "连接线类型",
        "电压范围", "额定电压", "操作角度范围", "外壳材质", "齿轮材质", "齿轮比",
        "齿轮级数", "堵转扭矩", "负载扭矩", "额定扭矩", "转动速度", "转动速度RPM",
        "输出齿类型", "重量", "附加说明",
    ],
    "积木舵机": [
        "外形尺寸", "额定电压", "电压范围", "最大砝码负载", "堵转扭矩", "空载速度",
        "通讯方式", "端子连接线",
    ],
    "标准齿轮箱": [
        "产品尺寸", "空载速度", "堵转扭矩", "齿轮比", "齿轮级数", "齿轮类型",
        "马达类型", "额定电压", "电压范围", "外壳材质",
    ],
    "翻盖电机": [
        "产品尺寸", "空载速度", "最大砝码负载", "堵转扭矩", "额定电压", "电压范围",
        "寿命", "马达类型", "外壳材质", "齿轮材质",
    ],
}


def clean(value: str | None) -> str:
    if not value:
        return ""
    value = value.replace("\n", "").replace("\u00a0", " ")
    value = re.sub(r"\s+", "", value)
    return value.strip()


def clean_code(value: str | None) -> str:
    return clean(value).replace("|", "")


def slug(value: str) -> str:
    safe = re.sub(r"[^A-Za-z0-9]+", "-", value).strip("-").lower()
    return safe or "product"


def parse_number(value: str) -> float | None:
    match = re.search(r"-?\d+(?:\.\d+)?", value.replace(",", ""))
    return float(match.group()) if match else None


def normalize_spec_values(category: str, row: list[str | None], inherited_size: str) -> list[str]:
    # PDF tables contain a blank merged column before the actual size value.
    size = clean(row[4]) or inherited_size
    if category in {"标准舵机", "异形舵机"}:
        return [size] + [clean(value) for value in row[5:16]]
    if category == "全金属舵机":
        return [size] + [clean(value) for value in row[5:22]]
    if category == "大扭矩舵机":
        return [size] + [clean(value) for value in row[5:25]]
    if category == "积木舵机":
        return [size] + [clean(value) for value in row[5:12]]
    if category in {"标准齿轮箱", "翻盖电机"}:
        return [size] + [clean(value) for value in row[5:14]]
    raise ValueError(f"Unsupported category: {category}")


def crop_product_image(
    page_image: Image.Image,
    page_width: float,
    page_height: float,
    cell: tuple[float, float, float, float],
    output_path: Path,
) -> None:
    scale_x = page_image.width / page_width
    scale_y = page_image.height / page_height
    x0, top, x1, bottom = cell
    inset = 3
    box = (
        max(0, int(x0 * scale_x) + inset),
        max(0, int(top * scale_y) + inset),
        min(page_image.width, int(x1 * scale_x) - inset),
        min(page_image.height, int(bottom * scale_y) - inset),
    )
    crop = page_image.crop(box).convert("RGB")

    background = Image.new("RGB", crop.size, "white")
    diff = ImageChops.difference(crop, background).convert("L")
    mask = diff.point(lambda pixel: 255 if pixel > 14 else 0)
    bbox = mask.getbbox()
    if bbox:
        crop = crop.crop(bbox)

    crop.thumbnail((280, 210), Image.Resampling.LANCZOS)
    card = Image.new("RGB", (320, 240), "white")
    card.paste(crop, ((320 - crop.width) // 2, (240 - crop.height) // 2))
    card.save(output_path, "WEBP", quality=88, method=6)


def main() -> None:
    if not PDF_PATH.exists():
        raise FileNotFoundError(PDF_PATH)
    IMAGE_DIR.mkdir(parents=True, exist_ok=True)

    products: list[dict[str, object]] = []
    category_counts: dict[str, int] = {}

    with pdfplumber.open(PDF_PATH) as pdf:
        for page_number, page in enumerate(pdf.pages, 1):
            page_category = CATEGORY_BY_PAGE[page_number]
            page_text = clean(page.extract_text() or "")
            expected_heading = f"{page_category}产品明细表"
            if expected_heading not in page_text:
                raise RuntimeError(
                    f"Category heading mismatch on page {page_number}: expected {expected_heading}"
                )
            tables = page.find_tables()
            if not tables:
                raise RuntimeError(f"No table found on page {page_number}")
            table = tables[0]
            rows = table.extract()

            render_path = RENDER_DIR / f"page-{page_number:02d}.png"
            if not render_path.exists():
                raise FileNotFoundError(f"Missing rendered page: {render_path}")
            rendered = Image.open(render_path).convert("RGB")

            inherited_name = ""
            inherited_size = ""
            inherited_image = ""

            for row_index, row in enumerate(rows[3:], 3):
                if len(row) < 3:
                    continue
                code = clean_code(row[2])
                if not code or not re.search(r"[A-Z].*\d", code):
                    continue
                category = CATEGORY_BY_CODE.get(code, page_category)

                explicit_name = clean(row[1])
                if explicit_name:
                    inherited_name = explicit_name
                name = explicit_name or inherited_name or code

                explicit_size = clean(row[4]) if len(row) > 4 else ""
                if explicit_size:
                    inherited_size = explicit_size

                cell = table.rows[row_index].cells[0] if row_index < len(table.rows) else None
                if explicit_name and cell:
                    image_name = f"p{page_number:02d}-{slug(code)}.webp"
                    crop_product_image(rendered, page.width, page.height, cell, IMAGE_DIR / image_name)
                    inherited_image = f"/catalog-products/{image_name}"

                values = normalize_spec_values(page_category, row, inherited_size)
                specs = {
                    label: value
                    for label, value in zip(SPEC_LABELS[page_category], values)
                    if value and value != "/"
                }

                torque_value = parse_number(specs.get("堵转扭矩", ""))
                rated_voltage = parse_number(specs.get("额定电压", ""))

                products.append(
                    {
                        "id": f"p{page_number:02d}-{row_index}-{slug(code)}",
                        "category": category,
                        "name": name,
                        "code": code,
                        "image": inherited_image,
                        "page": page_number,
                        "specs": specs,
                        "torqueValue": torque_value,
                        "ratedVoltage": rated_voltage,
                    }
                )
                category_counts[category] = category_counts.get(category, 0) + 1

    payload = json.dumps(products, ensure_ascii=False, indent=2)
    counts = json.dumps(category_counts, ensure_ascii=False, indent=2)
    DATA_PATH.write_text(
        "// Generated from the official 2026-06-30 PDF product catalog.\n"
        "// Run scripts/extract_catalog.py after the PDF or page renders change.\n\n"
        "export type CatalogProduct = {\n"
        "  id: string;\n"
        "  category: string;\n"
        "  name: string;\n"
        "  code: string;\n"
        "  image: string;\n"
        "  page: number;\n"
        "  specs: Record<string, string>;\n"
        "  torqueValue: number | null;\n"
        "  ratedVoltage: number | null;\n"
        "};\n\n"
        f"export const CATALOG_PRODUCTS: CatalogProduct[] = {payload};\n\n"
        f"export const CATALOG_COUNTS: Record<string, number> = {counts};\n",
        encoding="utf-8",
    )

    print(json.dumps({"total": len(products), "categories": category_counts}, ensure_ascii=False))


if __name__ == "__main__":
    main()
