from __future__ import annotations

import json
import re
from dataclasses import dataclass
from pathlib import Path

from docx import Document
from docx.enum.section import WD_SECTION_START
from docx.enum.text import WD_LINE_SPACING
from docx.oxml.ns import qn
from docx.shared import Cm, Pt


ROOT = Path("/Users/hedy/Desktop/zhct")
WORK_DIR = ROOT / "zhctprompt/product_software_copyright/ai-checkout-source-20260525-consume"
FRONT_DOCX = WORK_DIR / "智能菜品识别结算系统V1.0-前端源码材料30页-非订单结算版.docx"
BACK_DOCX = WORK_DIR / "智能菜品识别结算系统V1.0-后端源码材料30页-非订单结算版.docx"
FRONT_CLEAN = WORK_DIR / "frontend.selected.clean.txt"
BACK_CLEAN = WORK_DIR / "backend.selected.clean.txt"
MANIFEST_PATH = WORK_DIR / "source-selection-manifest.json"
CHECK_PATH = WORK_DIR / "pre-submission-check.md"

LINES_PER_PAGE = 50
PAGE_COUNT = 30
TOTAL_LINES = LINES_PER_PAGE * PAGE_COUNT


@dataclass(frozen=True)
class SourceSlice:
    path: str
    reason: str
    start: int | None = None
    end: int | None = None


FRONTEND_SOURCES: list[SourceSlice] = [
    SourceSlice(
        "zhctproject/store/public/static/src/view/restaurantManagement/aidish.vue",
        "PC端菜品识别图片学习数据列表、图片预览、单张删除、批量删除，对应/p/bill/dishesRecognizeImgList和/p/bill/dishesRecognizeImgDelete",
        1,
        793,
    ),
    SourceSlice(
        "zhctproject/ai_app/pagescourses/meals/identify.vue",
        "移动端拍照识别入口，处理图片上传、识别结果展示和识别失败修正入口",
        1,
        260,
    ),
    SourceSlice(
        "zhctproject/ai_app/pagescourses/meals/add-meal.vue",
        "识别后人工补充菜品页，处理菜品检索、分量输入和保存",
        1,
        260,
    ),
    SourceSlice(
        "zhctproject/store/public/static/src/view/restaurantManagement/weighingPlatform/index.vue",
        "PC端设备管理页面，涉及设备编号、设备类型、设备绑定菜品和设备菜品同步",
        1,
        288,
    ),
    SourceSlice(
        "zhctproject/store/public/static/src/view/restaurantManagement/addCuisine.vue",
        "PC端菜品资料新增编辑页面，涉及菜品名称、分类、重量、食材组成和图片资料维护",
        1,
        650,
    ),
    SourceSlice(
        "zhctproject/store/public/static/src/view/restaurantManagement/checkCuisine.vue",
        "PC端菜品详情查看和基础资料编辑页面，补充菜品查询、分类选择和保存校验",
        1,
        360,
    ),
]


BACKEND_SOURCES: list[SourceSlice] = [
    SourceSlice(
        "zhctproject/store/application/api/controller/Consume.php",
        "消费机菜品列表开放接口控制器，对应/api/consume/dishes",
        1,
        37,
    ),
    SourceSlice(
        "zhctproject/store/application/api/controller/Consume.php",
        "消费机菜品识别图片上传、列表和删除接口控制器，对应/api/consume/dishesRecognizeImgUpload、/api/consume/dishesRecognizeImgList、/api/consume/dishesRecognizeImgDelete",
        177,
        227,
    ),
    SourceSlice(
        "zhctproject/store/application/api/service/Consume.php",
        "消费机菜品列表、识别图片上传、识别图片列表和软删除核心实现",
        3190,
        3326,
    ),
    SourceSlice(
        "zhctproject/store/application/p/controller/Bill.php",
        "PC端账单侧菜品识别图片列表和删除控制器，对应/p/bill/dishesRecognizeImgList和/p/bill/dishesRecognizeImgDelete",
        347,
        398,
    ),
    SourceSlice(
        "zhctproject/store/application/p/logic/Bill.php",
        "PC端菜品识别学习图片按菜品聚合、搜索、分页和删除服务逻辑",
        1228,
        1332,
    ),
    SourceSlice(
        "zhctproject/store/application/p/controller/Equipment.php",
        "PC端设备管理控制器，覆盖设备列表、设备新增、编辑、删除、绑定菜品和设备筛选",
        1,
        383,
    ),
    SourceSlice(
        "zhctproject/store/application/p/logic/Equipment.php",
        "PC端设备管理服务，处理设备列表、编号唯一性、新增编辑、删除和设备菜品绑定",
        1,
        360,
    ),
    SourceSlice(
        "zhctproject/store/application/p/logic/Equipment.php",
        "PC端设备菜品绑定和消费设备查询补充逻辑",
        560,
        841,
    ),
    SourceSlice(
        "zhctproject/store/application/store/controller/dishes/Dishes.php",
        "PC端菜品管理控制器，覆盖菜品列表、新增、编辑、详情和删除入口",
        1,
        112,
    ),
    SourceSlice(
        "zhctproject/store/application/store/service/dishes/Dishes.php",
        "PC端菜品管理服务，处理菜品资料列表、详情、新增、编辑、删除和同步识别端菜品数据",
        37,
        216,
    ),
    SourceSlice(
        "zhctproject/store/application/p/logic/Dishes.php",
        "PC端菜品基础查询逻辑，处理菜品档案、菜品分组和识别所需菜品映射",
        1,
        109,
    ),
    SourceSlice(
        "zhctproject/store/application/model/Dishes.php",
        "菜品基础模型，支撑消费端菜品列表和识别图片关联查询",
    ),
    SourceSlice(
        "zhctproject/store/application/model/DishesRecognizeImg.php",
        "菜品识别学习图片模型，记录识别样本和软删除状态",
    ),
    SourceSlice(
        "zhctproject/store/application/model/DishesHardwareImg.php",
        "菜品硬件图片模型，支撑识别设备侧菜品图片读取",
    ),
    SourceSlice(
        "zhctproject/store/application/model/Equipment.php",
        "设备基础模型，支撑设备管理页面和识别终端设备信息维护",
    ),
    SourceSlice(
        "zhctproject/store/application/api/service/Plate.php",
        "电子菜牌设备菜品图片读取服务，补充设备侧菜品展示能力",
        1,
        68,
    ),
    SourceSlice(
        "zhctproject/store/application/api/service/Weigh.php",
        "称重设备菜品读取和排菜服务，补充设备侧菜品绑定和菜品识别数据来源",
        30,
        138,
    ),
]


EXCLUDED_SCOPE = [
    "登录认证",
    "权限角色",
    "通用工具类",
    "框架基础代码",
    "路由配置",
    "第三方依赖",
    "测试脚本",
    "静态编辑器插件",
]

FORBIDDEN_LINE_PATTERNS = [
    "http://",
    "https://",
    "Author",
    "author",
    "康比特",
    "CPT",
    "chinacpt",
    "密级",
    "password",
    "secretKey",
    "accessKey",
    "webhook",
    "登录",
    "权限",
    "orderPay",
    "cashier",
    "Payment",
    "payment",
    "pay_status",
    "pay_price",
    "order_no",
    "CONSUME_ORDER_PAY",
    "支付",
    "结算",
    "MQTT",
    "mqtt",
]


def strip_comments(source: str, php_hash: bool = False) -> str:
    source = re.sub(r"<!--.*?-->", "\n", source, flags=re.S)
    result: list[str] = []
    i = 0
    n = len(source)
    state = "normal"
    while i < n:
        ch = source[i]
        nxt = source[i + 1] if i + 1 < n else ""
        if state == "normal":
            if ch == "/" and nxt == "/":
                i += 2
                while i < n and source[i] not in "\r\n":
                    i += 1
                continue
            if ch == "/" and nxt == "*":
                i += 2
                while i < n - 1 and not (source[i] == "*" and source[i + 1] == "/"):
                    if source[i] in "\r\n":
                        result.append(source[i])
                    i += 1
                i += 2
                continue
            if php_hash and ch == "#":
                i += 1
                while i < n and source[i] not in "\r\n":
                    i += 1
                continue
            if ch == "'":
                state = "single"
                result.append(ch)
                i += 1
                continue
            if ch == '"':
                state = "double"
                result.append(ch)
                i += 1
                continue
            if ch == "`":
                state = "template"
                result.append(ch)
                i += 1
                continue
            result.append(ch)
            i += 1
            continue
        result.append(ch)
        if ch == "\\" and i + 1 < n:
            result.append(source[i + 1])
            i += 2
            continue
        if state == "single" and ch == "'":
            state = "normal"
        elif state == "double" and ch == '"':
            state = "normal"
        elif state == "template" and ch == "`":
            state = "normal"
        i += 1
    return "".join(result)


def line_is_forbidden(line: str) -> bool:
    lowered = line.lower()
    stripped = line.strip()
    if stripped.startswith("*") or stripped in {"*/", "/*"}:
        return True
    if any(token.lower() in lowered for token in FORBIDDEN_LINE_PATTERNS):
        return True
    if re.search(r"\b(login|passport|permission|auth|route|router|vendor|node_modules)\b", lowered):
        return True
    return False


def read_slice(item: SourceSlice) -> list[str]:
    full_path = ROOT / item.path
    text = full_path.read_text(encoding="utf-8", errors="ignore")
    lines = text.splitlines()
    start = item.start or 1
    end = item.end or len(lines)
    selected = "\n".join(lines[start - 1 : end])
    stripped = strip_comments(selected, php_hash=full_path.suffix == ".php")
    clean: list[str] = []
    for raw_line in stripped.splitlines():
        line = raw_line.replace("\t", "    ").rstrip()
        if not line.strip():
            continue
        if line_is_forbidden(line):
            continue
        clean.append(line)
    return clean


def collect_source(sources: list[SourceSlice]) -> tuple[list[str], list[dict[str, object]]]:
    output: list[str] = []
    manifest: list[dict[str, object]] = []
    for item in sources:
        before = len(output)
        lines = read_slice(item)
        output.extend(lines)
        manifest.append(
            {
                "path": str((ROOT / item.path).resolve()),
                "range": [item.start or 1, item.end or "EOF"],
                "reason": item.reason,
                "clean_lines": len(lines),
                "selected_output_lines": [before + 1, len(output)],
            }
        )
    if len(output) < TOTAL_LINES:
        raise RuntimeError(f"selected source has only {len(output)} clean lines, need {TOTAL_LINES}")
    return output[:TOTAL_LINES], manifest


def configure_run(run) -> None:
    run.font.name = "SimSun"
    run.font.size = Pt(6.8)
    run._element.rPr.rFonts.set(qn("w:ascii"), "SimSun")
    run._element.rPr.rFonts.set(qn("w:hAnsi"), "SimSun")
    run._element.rPr.rFonts.set(qn("w:eastAsia"), "SimSun")


def build_docx(path: Path, lines: list[str]) -> None:
    document = Document()
    section = document.sections[0]
    section.start_type = WD_SECTION_START.NEW_PAGE
    section.page_width = Cm(21)
    section.page_height = Cm(29.7)
    section.top_margin = Cm(0.7)
    section.bottom_margin = Cm(0.7)
    section.left_margin = Cm(0.8)
    section.right_margin = Cm(0.8)
    section.header_distance = Cm(0)
    section.footer_distance = Cm(0)
    normal = document.styles["Normal"]
    normal.font.name = "SimSun"
    normal.font.size = Pt(6.8)
    normal._element.rPr.rFonts.set(qn("w:eastAsia"), "SimSun")
    for page_index in range(PAGE_COUNT):
        start = page_index * LINES_PER_PAGE
        page_lines = lines[start : start + LINES_PER_PAGE]
        paragraph = document.add_paragraph()
        if page_index > 0:
            paragraph.paragraph_format.page_break_before = True
        paragraph.paragraph_format.space_before = Pt(0)
        paragraph.paragraph_format.space_after = Pt(0)
        paragraph.paragraph_format.line_spacing_rule = WD_LINE_SPACING.EXACTLY
        paragraph.paragraph_format.line_spacing = Pt(13.4)
        run = paragraph.add_run("\n".join(page_lines))
        configure_run(run)
    document.save(path)


def has_comment_marker(line: str) -> bool:
    stripped = line.strip()
    if stripped.startswith("//") or stripped.startswith("#") or stripped.startswith("*"):
        return True
    if "<!--" in stripped or "-->" in stripped or "/*" in stripped or "*/" in stripped:
        return True
    if re.search(r"(^|[^:])//", stripped):
        return True
    return False


def inspect_docx(path: Path) -> dict[str, int]:
    doc = Document(path)
    inline_shapes = len(doc.inline_shapes)
    tables = len(doc.tables)
    paragraphs = len(doc.paragraphs)
    line_count = sum(len(p.text.splitlines()) for p in doc.paragraphs)
    return {
        "paragraphs": paragraphs,
        "tables": tables,
        "inline_shapes": inline_shapes,
        "line_count": line_count,
    }


def write_manifest(front_lines: list[str], back_lines: list[str], front_manifest, back_manifest) -> None:
    payload = {
        "project": "智能菜品识别结算系统V1.0",
        "workspace": str(ROOT),
        "outputs": {
            "frontend_docx": str(FRONT_DOCX),
            "backend_docx": str(BACK_DOCX),
            "frontend_clean_text": str(FRONT_CLEAN),
            "backend_clean_text": str(BACK_CLEAN),
        },
        "page_count_each": PAGE_COUNT,
        "lines_per_page": LINES_PER_PAGE,
        "frontend_clean_lines": len(front_lines),
        "backend_clean_lines": len(back_lines),
        "frontend_source_selection": front_manifest,
        "backend_source_selection": back_manifest,
        "excluded_scope": EXCLUDED_SCOPE,
    }
    MANIFEST_PATH.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")


def write_check(front_lines: list[str], back_lines: list[str]) -> None:
    front_docx_stat = inspect_docx(FRONT_DOCX)
    back_docx_stat = inspect_docx(BACK_DOCX)
    checks = {
        "frontend_blank_lines": sum(1 for line in front_lines if not line.strip()),
        "backend_blank_lines": sum(1 for line in back_lines if not line.strip()),
        "frontend_comment_markers": sum(1 for line in front_lines if has_comment_marker(line)),
        "backend_comment_markers": sum(1 for line in back_lines if has_comment_marker(line)),
        "frontend_forbidden_terms": [t for t in FORBIDDEN_LINE_PATTERNS if any(t in line for line in front_lines)],
        "backend_forbidden_terms": [t for t in FORBIDDEN_LINE_PATTERNS if any(t in line for line in back_lines)],
        "frontend_docx": front_docx_stat,
        "backend_docx": back_docx_stat,
    }
    passed = (
        len(front_lines) == TOTAL_LINES
        and len(back_lines) == TOTAL_LINES
        and checks["frontend_blank_lines"] == 0
        and checks["backend_blank_lines"] == 0
        and checks["frontend_comment_markers"] == 0
        and checks["backend_comment_markers"] == 0
        and not checks["frontend_forbidden_terms"]
        and not checks["backend_forbidden_terms"]
        and front_docx_stat["tables"] == 0
        and back_docx_stat["tables"] == 0
        and front_docx_stat["inline_shapes"] == 0
        and back_docx_stat["inline_shapes"] == 0
    )
    content = [
        "# 智能菜品识别结算系统V1.0 软著源码材料提交前自查",
        "",
        f"- 本地工作目录：{WORK_DIR}",
        f"- 前端源码材料：{FRONT_DOCX}",
        f"- 后端源码材料：{BACK_DOCX}",
        f"- 选码清单：{MANIFEST_PATH}",
        f"- 前端页数目标：{PAGE_COUNT} 页",
        f"- 后端页数目标：{PAGE_COUNT} 页",
        f"- 每页源码行数：{LINES_PER_PAGE} 行",
        f"- 前端入册源码行数：{len(front_lines)} 行",
        f"- 后端入册源码行数：{len(back_lines)} 行",
        f"- 前端空行检查：{checks['frontend_blank_lines']} 行",
        f"- 后端空行检查：{checks['backend_blank_lines']} 行",
        f"- 前端注释标记检查：{checks['frontend_comment_markers']} 行",
        f"- 后端注释标记检查：{checks['backend_comment_markers']} 行",
        f"- 前端敏感标记检查：{checks['frontend_forbidden_terms']}",
        f"- 后端敏感标记检查：{checks['backend_forbidden_terms']}",
        f"- 前端Word结构：{front_docx_stat}",
        f"- 后端Word结构：{back_docx_stat}",
        "- 本版后端重点覆盖：/api/consume/dishes、/api/consume/dishesRecognizeImgUpload、/api/consume/dishesRecognizeImgList、/api/consume/dishesRecognizeImgDelete、/p/bill/dishesRecognizeImgList、/p/bill/dishesRecognizeImgDelete、PC端设备管理、PC端菜品管理和识别图片模型。",
        "- 本版前端重点覆盖：PC端识别图片学习数据管理、移动端拍照识别、人工补菜、PC端设备绑定菜品、PC端菜品资料新增编辑。",
        "- 已剔除：订单结算、收银支付、/api/consume/orderPay、订单支付服务和订单结算MQTT链路。",
        "- 已避开：登录、权限、通用工具类、框架基础代码、路由配置、第三方依赖、测试脚本和静态编辑器插件。",
        "",
        "## 自查结论",
        "",
        "- 通过：源码已去空行、去注释，并按前后端各 30 页、每页 50 行生成。" if passed else "- 需复查：清洗统计存在异常，请查看上方项目。",
    ]
    CHECK_PATH.write_text("\n".join(content) + "\n", encoding="utf-8")


def main() -> None:
    WORK_DIR.mkdir(parents=True, exist_ok=True)
    front_lines, front_manifest = collect_source(FRONTEND_SOURCES)
    back_lines, back_manifest = collect_source(BACKEND_SOURCES)
    FRONT_CLEAN.write_text("\n".join(front_lines) + "\n", encoding="utf-8")
    BACK_CLEAN.write_text("\n".join(back_lines) + "\n", encoding="utf-8")
    build_docx(FRONT_DOCX, front_lines)
    build_docx(BACK_DOCX, back_lines)
    write_manifest(front_lines, back_lines, front_manifest, back_manifest)
    write_check(front_lines, back_lines)
    print(json.dumps({
        "frontend_docx": str(FRONT_DOCX),
        "backend_docx": str(BACK_DOCX),
        "manifest": str(MANIFEST_PATH),
        "check": str(CHECK_PATH),
        "pages_each": PAGE_COUNT,
        "lines_per_page": LINES_PER_PAGE,
        "frontend_lines": len(front_lines),
        "backend_lines": len(back_lines),
    }, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
