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"
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/ai_app/pagescourses/meals/identify.vue",
        "拍照上传、菜品识别结果展示、重量编辑和保存入口",
        1,
        320,
    ),
    SourceSlice(
        "zhctproject/ai_app/pagescourses/meals/add-meal.vue",
        "手动补充餐食、菜品搜索、识别失败后的人工修正流程",
        1,
        300,
    ),
    SourceSlice(
        "zhctproject/ai_app/pagescourses/meals/meal-detail.vue",
        "识别后餐食详情、菜品明细、营养和重量展示",
        1,
        230,
    ),
    SourceSlice(
        "zhctproject/ai_app/settlement/ordering/cashier/index.vue",
        "结算台支付页面、支付方式选择、支付提交和查单处理",
        1,
        270,
    ),
    SourceSlice(
        "zhctproject/ai_app/settlement/ordering/selectionOfDishes.vue",
        "结算台菜品选择、分类切换和菜品数量调整",
        1,
        220,
    ),
    SourceSlice(
        "zhctproject/ai_app/settlement/ordering/placeAnOrder.vue",
        "结算台下单、订单金额计算和结算提交",
        1,
        260,
    ),
    SourceSlice(
        "zhctproject/ai_store/src/views/menu/dish/Index.vue",
        "电脑端智能菜品列表、查询、编辑、删除和状态展示",
    ),
    SourceSlice(
        "zhctproject/ai_store/src/views/menu/dish/Add.vue",
        "电脑端菜品新增编辑、图片样本、价格分类和保存",
        1,
        320,
    ),
    SourceSlice(
        "zhctproject/ai_store/src/api/menu/index.js",
        "电脑端菜品管理接口调用封装",
    ),
    SourceSlice(
        "zhctproject/ai_app/api/checkout.js",
        "结算台订单信息和提交接口",
    ),
]


BACKEND_SOURCES: list[SourceSlice] = [
    SourceSlice(
        "zhctproject/ai_api/app/terminal/service/equipment/Equipment.php",
        "结算台设备配对、门店绑定、消息配置和识别参数下发",
    ),
    SourceSlice(
        "zhctproject/ai_api/app/terminal/service/dishes/Dishes.php",
        "终端菜品详情、多菜品查询和识别所需菜品数据读取",
    ),
    SourceSlice(
        "zhctproject/ai_api/app/terminal/service/dishes/Img.php",
        "终端菜品学习图片新增、删除和样本列表处理",
    ),
    SourceSlice(
        "zhctproject/ai_api/app/terminal/service/meal/Meal.php",
        "终端就餐记录写入、识别结果落单和餐食明细生成",
    ),
    SourceSlice(
        "zhctproject/ai_api/app/api/controller/Checkout.php",
        "结算台订单信息和提交入口",
    ),
    SourceSlice(
        "zhctproject/ai_api/app/api/service/meal/Checkout.php",
        "餐食结算校验、订单初始化、菜品金额和明细计算",
        1,
        430,
    ),
    SourceSlice(
        "zhctproject/ai_api/app/api/service/cashier/MealPayment.php",
        "餐食订单支付、支付方式处理和支付结果回写",
        1,
        260,
    ),
    SourceSlice(
        "zhctproject/ai_api/app/store/service/dishes/Dishes.php",
        "电脑端菜品资料维护、图片样本和菜品库数据处理",
        1,
        260,
    ),
    SourceSlice(
        "zhctproject/ai_api/app/api/service/meal/DirectPay.php",
        "直接支付订单生成、餐食明细写入和支付前置校验",
        1,
        220,
    ),
    SourceSlice(
        "zhctproject/ai_api/app/api/service/meal/Order.php",
        "订单列表详情中的菜品明细、支付状态和取餐展示数据",
        1,
        300,
    ),
    SourceSlice(
        "zhctproject/ai_api/app/api/controller/MealCashier.php",
        "餐食收银台信息、支付提交和支付状态查询入口",
    ),
    SourceSlice(
        "zhctproject/ai_api/app/terminal/model/meal/MealInfos.php",
        "终端识别餐食明细新增、合并和单项修改",
    ),
    SourceSlice(
        "zhctproject/ai_api/app/terminal/model/dishes/Dishes.php",
        "终端菜品模型和识别菜品数据关联",
    ),
]


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

FORBIDDEN_LINE_PATTERNS = [
    "http://",
    "https://",
    "Author",
    "author",
    "康比特",
    "CPT",
    "chinacpt",
    "密级",
]


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()
    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(7.2)
    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(7.2)
    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(14.2)
        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 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:
    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)],
    }
    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"]
    )
    content = [
        "# 智能菜品识别结算系统V1.0 软著源码材料提交前自查",
        "",
        f"- 本地工作目录：{WORK_DIR}",
        f"- 前端源码材料：{FRONT_DOCX}",
        f"- 后端源码材料：{BACK_DOCX}",
        f"- 前端清洗源码：{FRONT_CLEAN}",
        f"- 后端清洗源码：{BACK_CLEAN}",
        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']}",
        "- 核心覆盖：菜品拍照识别、识别结果修正、手动添加菜品、结算提交、支付处理、菜品样本维护、设备配对和电脑端菜品维护。",
        "- 已避开：登录、权限、通用工具类、框架基础代码、路由配置、第三方依赖、测试脚本和静态编辑器插件。",
        "",
        "## 自查结论",
        "",
        "- 通过：源码已去空行、去注释，并按前后端各 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()
