#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Create an isolated DRV8316 DRVOFF pull-up correction candidate."""
from __future__ import annotations
import hashlib
import json
import re
import shutil
import uuid
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]
SRC = ROOT / "work/tri-product-continuation-2026-10-09.nosync/hardware_source_localization_v140/硬件/drill_motor"
OUT = Path(__file__).with_name("drill_motor_run_gate_r1")
PROJECT = OUT / "drill_motor"
BOARD_DIR = PROJECT / "evt"
SCH = PROJECT / "drill_motor.kicad_sch"
BOARD = BOARD_DIR / "drill_motor_evt_a1_run_gate.kicad_pcb"


def block_at(text: str, token: str, predicate) -> tuple[int, int, str]:
    pos = 0
    while True:
        start = text.find(token, pos)
        if start < 0:
            raise RuntimeError(f"block not found: {token}")
        depth = 0
        quoted = False
        escaped = False
        end = None
        for i in range(start, len(text)):
            ch = text[i]
            if quoted:
                if escaped:
                    escaped = False
                elif ch == "\\":
                    escaped = True
                elif ch == '"':
                    quoted = False
            else:
                if ch == '"':
                    quoted = True
                elif ch == "(":
                    depth += 1
                elif ch == ")":
                    depth -= 1
                    if depth == 0:
                        end = i + 1
                        break
        if end is None:
            raise RuntimeError(f"unbalanced block: {token}")
        body = text[start:end]
        if predicate(body):
            return start, end, body
        pos = end


def unique_uuids(block: str) -> str:
    return re.sub(r'\(uuid "[0-9a-fA-F-]+"\)', lambda _: f'(uuid "{uuid.uuid4()}")', block)


def patch_schematic() -> None:
    text = SCH.read_text(encoding="utf-8")
    _, _, r4 = block_at(text, '(symbol (lib_id "Project:R")', lambda b: '(property "Reference" "R4"' in b)
    r15 = r4.replace('(property "Reference" "R4"', '(property "Reference" "R15"', 1)
    r15 = r15.replace('(property "Value" "100k"', '(property "Value" "10k 1%"', 1)
    r15 = r15.replace('(reference "R4")', '(reference "R15")', 1)
    r15 = r15.replace('39.37', '355.6')
    r15 = unique_uuids(r15)
    text = text.replace('(title "Drill motor stage A0")', '(title "Drill motor stage A1 RUN-GATE FIX")', 1)
    text = text.replace('(rev "A0")', '(rev "A1-RUN-GATE")', 1)
    text = text.replace('DRILL MOTOR POWER STAGE - ENGINEERING PROTOTYPE A0', 'DRILL MOTOR POWER STAGE - ENGINEERING PROTOTYPE A1 RUN-GATE FIX', 1)
    text = text.replace(
        'DRVOFF is pulled to AVDD through Q1: gate low/reset = OFF; only DRV_RUN_REQ high turns Q1 on and permits output.',
        'R15=10k pulls DRVOFF up to DRV_AVDD; Q1 sinks DRVOFF only when DRV_RUN_REQ is high. Gate low/reset leaves the driver disabled.'
    )
    wire_label = (
        '(wire (pts (xy 355.6 240.03) (xy 363.22 240.03)) (stroke (width 0) (type default)) '
        f'(uuid "{uuid.uuid4()}"))'
        f'(label "DRV_AVDD" (at 363.22 240.03 0) (effects (font (size 1 1)) (justify left bottom)) (uuid "{uuid.uuid4()}"))'
        '(wire (pts (xy 355.6 250.19) (xy 363.22 250.19)) (stroke (width 0) (type default)) '
        f'(uuid "{uuid.uuid4()}"))'
        f'(label "DRV_OFF" (at 363.22 250.19 0) (effects (font (size 1 1)) (justify left bottom)) (uuid "{uuid.uuid4()}"))'
    )
    text = text.rstrip()
    if not text.endswith(")"):
        raise RuntimeError("schematic root close missing")
    text = text[:-1] + r15 + wire_label + ")\n"
    SCH.write_text(text, encoding="utf-8")


def pad_net_replace(block: str, pad_number: str, net_name: str) -> str:
    start, end, pad = block_at(block, f'(pad "{pad_number}"', lambda _: True)
    pad = re.sub(r'\(net "[^"]+"\)', f'(net "{net_name}")', pad, count=1)
    return block[:start] + pad + block[end:]


def footprint_with_ref(text: str, ref: str) -> tuple[int, int, str]:
    return block_at(text, '(footprint "', lambda b: f'(property "Reference" "{ref}"' in b)


def patch_board() -> None:
    text = BOARD.read_text(encoding="utf-8")
    _, _, resistor = footprint_with_ref(text, "R4")
    new = resistor.replace('(property "Reference" "R4"', '(property "Reference" "R15"', 1)
    new = new.replace('(property "Value" "100k"', '(property "Value" "10k 1%"', 1)
    new = re.sub(r'\(at [^()\s]+ [^()\s]+(?: [^()\s]+)?\)', '(at 102.5 17.4)', new, count=1)
    new = unique_uuids(new)
    new = pad_net_replace(new, "1", "/DRV_AVDD")
    new = pad_net_replace(new, "2", "/DRV_OFF")

    def track(start: tuple[float, float], end: tuple[float, float], net: str) -> str:
        return (
            f'\n\t(segment (start {start[0]} {start[1]}) (end {end[0]} {end[1]}) '
            f'(width 0.2) (layer "F.Cu") (net "{net}") (uuid "{uuid.uuid4()}"))\n'
        )

    # Split the AVDD copper at an exact point so KiCad sees an unambiguous T-junction.
    avdd_point = (101.675, 18.9386)
    net, start, end = "/DRV_AVDD", (82.7687, 18.9386), (103.8351, 18.9386)
    found = False
    for a, b, segment in list((a, b, body) for a, b, body in iter_blocks(text, "(segment")):
        if f'(net "{net}")' in segment and f'(start {start[0]} {start[1]})' in segment and f'(end {end[0]} {end[1]})' in segment:
            width = re.search(r'\(width ([^ )]+)\)', segment).group(1)
            layer = re.search(r'\(layer "([^"]+)"\)', segment).group(1)
            text = text[:a] + (
                f'(segment (start {start[0]} {start[1]}) (end {avdd_point[0]} {avdd_point[1]}) (width {width}) (layer "{layer}") (net "{net}") (uuid "{uuid.uuid4()}"))\n'
                f'\t(segment (start {avdd_point[0]} {avdd_point[1]}) (end {end[0]} {end[1]}) (width {width}) (layer "{layer}") (net "{net}") (uuid "{uuid.uuid4()}"))'
            ) + text[b:]
            found = True
            break
    if not found:
        raise RuntimeError("existing DRV_AVDD track not found for split")
    new_tracks = (
        track((103.325, 17.4), (100.9472, 14.257), "/DRV_OFF")
        + track((101.675, 17.4), avdd_point, "/DRV_AVDD")
    )
    insert_at = text.find("\n\t(segment")
    if insert_at < 0:
        raise RuntimeError("board track insertion point not found")
    text = text[:insert_at] + "\n\t" + new + new_tracks + text[insert_at:]
    BOARD.write_text(text, encoding="utf-8")


def iter_blocks(text: str, token: str):
    pos = 0
    while True:
        start = text.find(token, pos)
        if start < 0:
            return
        depth = 0
        quoted = False
        escaped = False
        end = None
        for i in range(start, len(text)):
            ch = text[i]
            if quoted:
                if escaped:
                    escaped = False
                elif ch == "\\":
                    escaped = True
                elif ch == '"':
                    quoted = False
            else:
                if ch == '"':
                    quoted = True
                elif ch == "(":
                    depth += 1
                elif ch == ")":
                    depth -= 1
                    if depth == 0:
                        end = i + 1
                        break
        if end is None:
            raise RuntimeError(f"unbalanced block: {token}")
        yield start, end, text[start:end]
        pos = end


def main() -> None:
    if OUT.exists():
        shutil.rmtree(OUT)
    shutil.copytree(SRC, PROJECT)
    original_board = BOARD_DIR / "drill_motor_evt_a0.kicad_pcb"
    original_pro = BOARD_DIR / "drill_motor_evt_a0.kicad_pro"
    shutil.copy2(original_board, BOARD)
    shutil.copy2(original_pro, BOARD_DIR / "drill_motor_evt_a1_run_gate.kicad_pro")
    patch_schematic()
    patch_board()
    (OUT / "README.md").write_text(
        "# 电磨 DRV8316 运行请求硬件关断修订候选 A1\n\n"
        "状态：隔离工程候选；未释放制板或量产。\n\n"
        "修订：新增 R15=10k 1% 从 DRV_AVDD 上拉 DRVOFF；原 2N7002 Q1 在 DRV_RUN_REQ 高时拉低 DRVOFF，使驱动允许输出。MCU未上电或 RUN_REQ 低时，R6 将 Q1 栅极下拉，R15 将 DRVOFF 拉高关闭六个功率 MOSFET。\n\n"
        "选择 10k 延续原工程 README 的设计意图；需用真实手柄验证温度、启动/堵转电流、限流和扭矩/转速后冻结。现有电机限流仍按约 2A 原型值，不作等效或量产结论。\n\n"
        "验证顺序：重新导出原理图 XML 网表；比对新原理图/PCB 每个管脚网络；运行 KiCad ERC/DRC；后续样板还需测量 DRVOFF 波形、故障停机、堵转与转速扭矩曲线。\n",
        encoding="utf-8",
    )
    manifest = {
        "status": "DERIVED_CANDIDATE_CREATED_VALIDATION_PENDING",
        "source_schematic_sha256": hashlib.sha256((SRC / "drill_motor.kicad_sch").read_bytes()).hexdigest(),
        "source_board_sha256": hashlib.sha256(original_board.read_bytes()).hexdigest(),
        "candidate_schematic": str(SCH),
        "candidate_board": str(BOARD),
        "change": "R15 10k 1% from DRV_AVDD to DRV_OFF; route Q1 gate pull-down remains unchanged",
        "candidate_schematic_sha256": hashlib.sha256(SCH.read_bytes()).hexdigest(),
        "candidate_board_sha256": hashlib.sha256(BOARD.read_bytes()).hexdigest(),
    }
    (OUT / "candidate_manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(json.dumps(manifest, ensure_ascii=False, indent=2))

if __name__ == "__main__":
    main()
