"""接口3：马克笔手绘发际线检测（黑帽响应图 + 端点锚定 Dijkstra 最小路径）。

源自 /home/xsl/headmark 调研结论：全局灰度阈值不可用（笔迹平均灰度反高于阈值、
与皮肤阴影分布重叠）；黑帽变换响应"比局部邻域暗的细结构"，叠加 ROI + 两鬓角锚点间
最小代价路径，对抬头纹/眉毛/发丝鲁棒。复用接口2 的 ROI（额头上部 ∩ 头部分割）。
"""
from __future__ import annotations

import cv2
import numpy as np
from skimage.graph import route_through_array

from .mask import forehead_upper_region, head_silhouette

# 鬓角锚点（MediaPipe canonical 索引）：21 左、251 右
ANCHOR_LEFT = 21
ANCHOR_RIGHT = 251
# 拒识阈值：路径平均黑帽响应低于此值 → 判"未检测到画线"（待真实图标定）
MIN_MEAN_RESPONSE = 8.0


def _blackhat(gray: np.ndarray, w: int) -> np.ndarray:
    k = max(15, int(w * 0.025) | 1)
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (k, k))
    return cv2.morphologyEx(gray, cv2.MORPH_BLACKHAT, kernel).astype(np.float32)


def _snap_anchor(bh_roi: np.ndarray, x: int, y: int, w: int):
    """在 (x,y) 周围窗口内吸附到黑帽响应最大处，返回 (row, col)。"""
    win = max(8, int(w * 0.03))
    h, ww = bh_roi.shape
    x0, x1 = max(0, x - win), min(ww, x + win)
    y0, y1 = max(0, y - win), min(h, y + win)
    sub = bh_roi[y0:y1, x0:x1]
    if sub.size == 0 or sub.max() <= 0:
        return (int(np.clip(y, 0, h - 1)), int(np.clip(x, 0, ww - 1)))
    dy, dx = np.unravel_index(int(np.argmax(sub)), sub.shape)
    return (y0 + dy, x0 + dx)


def detect_marker_hairline(marked_bgr: np.ndarray, landmarks_mp: np.ndarray,
                           parse_map: np.ndarray, min_mean_response: float = MIN_MEAN_RESPONSE):
    """检测手绘发际线，返回路径 (N,2) row,col；未检出/被拒识返回 None。"""
    h, w = marked_bgr.shape[:2]
    roi = cv2.bitwise_and(forehead_upper_region(landmarks_mp, w, h),
                          head_silhouette(parse_map)) > 0
    if roi.sum() == 0:
        return None

    gray = cv2.cvtColor(marked_bgr, cv2.COLOR_BGR2GRAY)
    bh = _blackhat(gray, w)
    bh_roi = bh * roi

    al = _snap_anchor(bh_roi, int(landmarks_mp[ANCHOR_LEFT, 0] * w),
                      int(landmarks_mp[ANCHOR_LEFT, 1] * h), w)
    ar = _snap_anchor(bh_roi, int(landmarks_mp[ANCHOR_RIGHT, 0] * w),
                      int(landmarks_mp[ANCHOR_RIGHT, 1] * h), w)

    cost = (bh.max() - bh) + 1.0
    cost[~roi] = 1e6                                  # 禁止路径走出 ROI
    path, _ = route_through_array(cost, al, ar, fully_connected=True, geometric=True)
    path = np.asarray(path)

    # 拒识：路径平均黑帽响应过低 → 没画线（强行找出的伪路径）
    if float(bh[path[:, 0], path[:, 1]].mean()) < min_mean_response:
        return None
    return path


def path_to_curve_mask(path: np.ndarray, h: int, w: int, thickness: int = 3) -> np.ndarray:
    """把路径画成曲线 mask（uint8 0/255），用作遮罩下边界 / 重画干净线。"""
    m = np.zeros((h, w), np.uint8)
    pts = path[:, ::-1].reshape(-1, 1, 2)            # (row,col)→(x,y)
    cv2.polylines(m, [pts], False, 255, thickness, lineType=cv2.LINE_AA)
    return m


if __name__ == "__main__":
    import sys
    from .service import get_landmarker, get_parser

    path_img = sys.argv[1] if len(sys.argv) > 1 else "/home/xsl/headmark/test_image/input1.png"
    img = cv2.imread(path_img)
    if img is None:
        print(f"无法读取 {path_img}"); sys.exit(1)
    h, w = img.shape[:2]
    rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    lm = get_landmarker().detect(rgb)
    if lm is None:
        print("未检出人脸"); sys.exit(1)
    pm = get_parser().parse(rgb)
    p = detect_marker_hairline(img, lm, pm)
    if p is None:
        print("未检测到发际线划线（拒识）"); sys.exit(0)
    print(f"检测到画线：{len(p)} 点")
    vis = img.copy()
    cv2.polylines(vis, [p[:, ::-1].reshape(-1, 1, 2)], False, (0, 0, 255), 2)
    import os
    os.makedirs("tests/output", exist_ok=True)
    name = os.path.splitext(os.path.basename(path_img))[0]
    cv2.imwrite(f"tests/output/marker_{name}.png", vis)
    print(f"saved tests/output/marker_{name}.png")
