# -*- coding: utf-8 -*-
"""CẮT ĐOẠN — gộp các khoá rời rạc thành Doan (đơn vị đưa vào chỉ mục).

VÌ SAO không đánh chỉ mục THẲNG từng khoá: một khoá đơn lẻ (một câu, một
nhãn nút) quá ngắn để mang đủ ngữ cảnh cho BM25/embedding chấm điểm tốt,
và quá vụn để trích dẫn nguồn cho khách đọc ("nguồn: nút Lưu" thì vô
nghĩa). Gộp nhiều khoá LIỀN NHAU trong cùng một tệp thành một đoạn ~250 từ
tái tạo lại đúng mạch văn của trang — đó là lý do bước gộp phải giữ
NGUYÊN THỨ TỰ xuất hiện, không được sắp lại theo bảng chữ cái hay độ dài.
"""
from __future__ import annotations

import sys
import os
from typing import List, Dict, Any

try:
    from ..giao_uoc import Doan, doan_kieu
except ImportError:
    sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
    from giao_uoc import Doan, doan_kieu


# Ngưỡng ~250 từ: đủ dài để một đoạn mang trọn một ý (vài bước hướng dẫn,
# một mục FAQ) nhưng không dài tới mức nhét nhiều chủ đề khác nhau vào
# chung một đoạn — nhồi nhiều chủ đề thì điểm khớp của BM25/embedding bị
# pha loãng, đoạn không còn "về" một thứ gì cụ thể.
NGUONG_TU_MOI_DOAN = 250

# 15%: đủ để câu cuối đoạn trước làm "cầu" ngữ cảnh cho đoạn sau (vd một
# bước hướng dẫn bị cắt ngang thì bước liền trước vẫn còn trong đoạn mới),
# mà không lặp lại nhiều tới mức một khoá bị đếm 2-3 lần trong thống kê
# so_tu và tốn chỗ chỉ mục một cách vô ích.
TY_LE_CHONG_LAP = 0.15

# Khoá <= 3 từ đứng MỘT MÌNH (vd "Lưu", "Xem thêm", "Đồng ý") là nhãn nút,
# không tự nó mang nghĩa để tìm kiếm — nhưng nếu nó nằm GIỮA một đoạn dài
# hơn (vd token đệm giữa hai câu hướng dẫn) thì vẫn giữ, vì lúc đó nó là
# một phần của mạch văn chứ không đứng riêng.
NGUONG_TU_QUA_NGAN = 3


def _dem_tu(van_ban: str) -> int:
    """Đếm từ kiểu đơn giản (tách theo khoảng trắng).

    VÌ SAO không tách âm tiết tiếng Việt "đúng nghĩa" (mỗi âm tiết viết
    rời đã được coi là một "từ" theo cách gõ nguồn): đây chỉ là thước đo
    độ dài để quyết định ĐIỂM CẮT đoạn, không phải phân tích ngôn ngữ học
    — sai lệch vài từ không ảnh hưởng chất lượng cắt, mà tách âm tiết cho
    đúng nghĩa từ ghép tiếng Việt cần từ điển, lại là việc thừa ở đây.
    """
    return len(van_ban.split())


def _lay_duoi_chong_lap(cac_khoa: List[Dict[str, Any]], so_tu_muc_tieu: float) -> List[Dict[str, Any]]:
    """Lấy vài khoá CUỐI của một đoạn đã chốt, đủ ~so_tu_muc_tieu từ, để nối
    sang đầu đoạn kế tiếp (chồng lấn)."""
    if so_tu_muc_tieu <= 0:
        return []
    duoi: List[Dict[str, Any]] = []
    tong = 0
    for kh in reversed(cac_khoa):
        duoi.insert(0, kh)
        tong += _dem_tu(kh['van_ban_vi'])
        if tong >= so_tu_muc_tieu:
            break
    return duoi


def _cat_mot_nhom(cac_khoa: List[Dict[str, Any]]) -> List[List[Dict[str, Any]]]:
    """Cắt MỘT nhóm (đã cùng nguồn_tep/loai/san_pham, giữ nguyên thứ tự
    trong tệp) thành các đoạn ~NGUONG_TU_MOI_DOAN từ, chồng lấn ~15%."""
    cac_doan: List[List[Dict[str, Any]]] = []
    hien_tai: List[Dict[str, Any]] = []
    tu_hien_tai = 0

    for kh in cac_khoa:
        so_tu_kh = _dem_tu(kh['van_ban_vi'])

        # Chỉ cắt khi đoạn hiện tại ĐÃ có nội dung — một khoá là đơn vị
        # nguyên tử (một câu nguyên trong mã nguồn), không được xé đôi để
        # vừa khít ngưỡng, nên khoá đầu tiên của một đoạn luôn được nhận
        # dù một mình nó đã vượt ngưỡng.
        if hien_tai and tu_hien_tai + so_tu_kh > NGUONG_TU_MOI_DOAN:
            cac_doan.append(hien_tai)
            hien_tai = _lay_duoi_chong_lap(hien_tai, NGUONG_TU_MOI_DOAN * TY_LE_CHONG_LAP)
            tu_hien_tai = sum(_dem_tu(k['van_ban_vi']) for k in hien_tai)

        hien_tai.append(kh)
        tu_hien_tai += so_tu_kh

    if hien_tai:
        cac_doan.append(hien_tai)
    return cac_doan


def _ten_trang(nguon_tep: str) -> str:
    """Suy 'ten_trang' dùng trong mã của đoạn CÓ NEO, vd
    'guilde/dcabot.php' -> 'dcabot'. Chỉ dùng tên tệp (không kèm thư mục)
    vì đó là phần người đọc mã dễ nhận ra nhất, và mã chỉ cần DUY NHẤT
    trong phạm vi (san_pham, ten_trang, neo) chứ không cần là cả đường dẫn."""
    return os.path.splitext(os.path.basename(nguon_tep))[0].lower()


def cat_tat_ca(danh_sach_khoa: List[Dict[str, Any]]) -> List[Doan]:
    """Gộp danh sách khoá (từ trich_tat_ca()) thành list[Doan] sẵn sàng đánh chỉ mục.

    Mỗi PHẦN CÓ NEO (trich.py đã gắn kèm khoá 'neo' + đường dẫn/tiêu đề
    riêng của phần đó) trở thành một NHÓM CẮT RIÊNG, tách khỏi phần còn lại
    của trang — nhờ vậy "Khắc phục lỗi thường gặp" không còn bị hoà lẫn vào
    "Cấu hình tham số" chỉ vì cả hai đều ~250 từ liền kề trong tệp. Trang
    KHÔNG có neo nào thì mọi khoá đều neo=None, rơi về đúng nhóm DUY NHẤT
    (nguon_tep, loai, san_pham, None) như hành vi cũ — không mất đoạn nào.
    """

    # B1 — gom theo (nguon_tep, loai, san_pham, neo), GIỮ NGUYÊN thứ tự
    # xuất hiện trong danh_sach_khoa (chính là thứ tự quét tệp + thứ tự
    # trong tệp mà trich.py đã tạo ra) — dict Python giữ thứ tự chèn từ
    # 3.7 nên không cần cấu trúc đặc biệt nào khác. Thêm 'neo' vào khoá
    # nhóm là toàn bộ thay đổi so với bản cũ: một phần có neo không còn bị
    # gộp chung với các phần liền kề khác neo (hoặc không neo) của CÙNG
    # một tệp.
    nhom: Dict[tuple, List[Dict[str, Any]]] = {}
    thu_tu_nhom: List[tuple] = []
    for kh in danh_sach_khoa:
        khoa_nhom = (kh['nguon_tep'], kh['loai'], kh['san_pham'], kh.get('neo'))
        if khoa_nhom not in nhom:
            nhom[khoa_nhom] = []
            thu_tu_nhom.append(khoa_nhom)
        nhom[khoa_nhom].append(kh)

    # Số thứ tự trong ma đếm theo TỪNG khoá đếm (xem hai nhánh dưới) xuyên
    # suốt toàn bộ kết quả — nhiều tệp khác nhau có thể cùng sản phẩm+loại
    # (vd nhiều tệp guilde/ của dcabot), số thứ tự phải nối tiếp qua các
    # tệp đó chứ không reset về 1 mỗi tệp.
    bo_dem_stt: Dict[tuple, int] = {}
    ket_qua: List[Doan] = []

    for khoa_nhom in thu_tu_nhom:
        cac_doan_tho = _cat_mot_nhom(nhom[khoa_nhom])
        for doan_tho in cac_doan_tho:
            if len(doan_tho) == 1 and _dem_tu(doan_tho[0]['van_ban_vi']) <= NGUONG_TU_QUA_NGAN:
                # Đoạn chỉ có ĐÚNG một khoá và khoá đó quá ngắn — nó đứng
                # một mình (không có gì trước/sau nó ở đoạn này để cho nó
                # ngữ cảnh), tức là nhãn nút kiểu "Lưu"/"Đồng ý". Vô nghĩa
                # với tìm kiếm nên bỏ, KHÔNG đánh số thứ tự cho nó.
                continue

            nhan_san_pham = doan_tho[0]['san_pham'] or 'chung'
            neo = doan_tho[0].get('neo')
            tieu_de = doan_tho[0]['tieu_de']

            if neo:
                # Mã kiểu 'dcabot/dcabot/troubleshooting' — MANG THEO tên
                # trang + tên neo, để trích nguồn nhìn vào mã là biết ngay
                # phần nào, không phải suy từ số thứ tự vô nghĩa. Mảnh thứ
                # hai trở đi (phần dài phải cắt nhỏ) thêm hậu tố '-02', '-03'.
                ten_trang = _ten_trang(doan_tho[0]['nguon_tep'])
                khoa_dem = (nhan_san_pham, ten_trang, neo)
                stt = bo_dem_stt.get(khoa_dem, 0) + 1
                bo_dem_stt[khoa_dem] = stt
                ma = (f'{nhan_san_pham}/{ten_trang}/{neo}' if stt == 1
                      else f'{nhan_san_pham}/{ten_trang}/{neo}-{stt:02d}')
            else:
                # KHÔNG neo — giữ NGUYÊN hành vi cũ, không đổi định dạng mã
                # để không phá chỉ mục/đường dẫn của những trang chưa có neo.
                nhan_loai = doan_tho[0]['loai'] or 'khac'
                khoa_dem = (nhan_san_pham, nhan_loai)
                stt = bo_dem_stt.get(khoa_dem, 0) + 1
                bo_dem_stt[khoa_dem] = stt
                ma = f'{nhan_san_pham}/{nhan_loai}/{stt:04d}'

            van_ban_vi = '\n\n'.join(k['van_ban_vi'] for k in doan_tho)
            van_ban_en = '\n\n'.join(k['van_ban_en'] for k in doan_tho if k['van_ban_en'])

            ket_qua.append(Doan(
                ma=ma,
                van_ban_vi=van_ban_vi,
                van_ban_en=van_ban_en,
                san_pham=doan_tho[0]['san_pham'],
                loai=doan_tho[0]['loai'],
                duong_dan=doan_tho[0]['duong_dan'],
                tieu_de=tieu_de,
                nguon_tep=doan_tho[0]['nguon_tep'],
                so_tu=_dem_tu(van_ban_vi),
                kieu=doan_kieu(neo, tieu_de),
            ))

    return ket_qua


# =====================================================================
# CHẠY TRỰC TIẾP
# =====================================================================

if __name__ == '__main__':
    try:
        from .trich import trich_tat_ca
    except ImportError:
        sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
        from trich import trich_tat_ca

    from collections import Counter

    cac_khoa = trich_tat_ca()
    cac_doan = cat_tat_ca(cac_khoa)

    tong = len(cac_doan)
    tong_tu = sum(d.so_tu for d in cac_doan)
    tb_tu = tong_tu / tong if tong else 0

    print(f'Tổng số đoạn: {tong}')
    print(f'Số từ trung bình mỗi đoạn: {tb_tu:.1f}')
    print()
    print('--- Phân bố theo sản phẩm ---')
    dem_sp = Counter(d.san_pham or '(dùng chung / không rõ)' for d in cac_doan)
    for sp, sl in dem_sp.most_common():
        print(f'{sp:30} {sl:5d}  ({100 * sl / tong:.1f}%)')
