import re
import csv
import io
from dataclasses import dataclass, field
from typing import List, Optional, Dict, Tuple


CHUNK_STRATEGIES = {
    "laws": {
        "name": "按条文切片",
        "mode": "by_article",
        "description": "适用于法律法规类文档，按'第N条'切分为独立条文",
        "default_collection": "market_regulation_laws",
        "payload_type": "law_article",
    },
    "standards": {
        "name": "按违法事项切片",
        "mode": "by_violation",
        "description": "适用于裁量基准/清单类文档，按违法事项/条目切分",
        "default_collection": "market_regulation_laws",
        "payload_type": "discretion_standard",
    },
    "cases": {
        "name": "按案例切片",
        "mode": "by_case",
        "description": "适用于典型案例类文档，每个案例作为一个整体切片",
        "default_collection": "case_precedents",
        "payload_type": "case_precedent",
    },
    "other": {
        "name": "整文档入库",
        "mode": "whole_doc",
        "description": "不切片，整文档作为一个向量点入库（适用于短文档）",
        "default_collection": "market_regulation_laws",
        "payload_type": "md_document",
    },
}

STRATEGY_LIST = [
    {"key": "auto", "name": "自动识别", "description": "根据文档分类自动选择最佳切片策略"},
    {"key": "by_article", "name": "按条文切片", "description": "适用于法律法规，按'第N条'切分", "applicable_category": "laws"},
    {"key": "by_violation", "name": "按违法事项切片", "description": "适用于裁量基准，按违法事项/条目切分", "applicable_category": "standards"},
    {"key": "by_item", "name": "按清单条目切片", "description": "适用于清单文件，按条目键值对切分", "applicable_category": "standards"},
    {"key": "by_case", "name": "按案例切片", "description": "适用于典型案例，每个案例作为一个整体", "applicable_category": "cases"},
    {"key": "whole_doc", "name": "整文档入库", "description": "不切片，整文档作为一个向量点", "applicable_category": "other"},
]


@dataclass
class SeverityLevel:
    level: str = ""
    circumstance: str = ""
    penalty: str = ""


@dataclass
class DocumentChunk:
    chunk_id: str = ""
    source_file: str = ""
    source_type: str = ""

    article_number: str = ""
    referenced_laws: Optional[List[str]] = None

    category: str = ""
    seq_number: int = 0
    violation: str = ""
    legal_basis: str = ""
    legal_basis_detail: str = ""
    severity_levels: Optional[List[SeverityLevel]] = None

    list_type: str = ""
    applicable_conditions: str = ""
    violated_provision: str = ""
    penalty_basis: str = ""

    case_type: str = ""
    case_id: str = ""
    violation_type: str = ""
    violation_category: str = ""
    penalty_summary: str = ""
    discretion_level: str = ""
    legal_basis_list: Optional[List[str]] = None

    content: str = ""
    full_text: str = ""
    metadata: Optional[Dict] = None


@dataclass
class ChunkResult:
    chunks: List[DocumentChunk] = field(default_factory=list)
    strategy_used: str = ""
    total_chunks: int = 0
    strategy_display_name: str = ""


@dataclass
class ChunkPreview:
    chunks: List[Dict] = field(default_factory=list)
    strategy_used: str = ""
    total_chunks: int = 0
    strategy_display_name: str = ""
    estimated_qdrant_points: int = 0


def _split_by_pattern(content: str, pattern: re.Pattern) -> List[Tuple[str, str]]:
    matches = list(pattern.finditer(content))
    if not matches:
        return []
    sections = []
    for i, match in enumerate(matches):
        start = match.start()
        end = matches[i + 1].start() if i + 1 < len(matches) else len(content)
        label = match.group(1) if match.groups() else match.group(0)
        section_content = content[start:end].strip()
        sections.append((label, section_content))
    return sections


def _extract_law_refs(text: str) -> List[str]:
    pattern = re.compile(r'《([^》]+)》')
    return list(dict.fromkeys(pattern.findall(text)))


def _parse_markdown_table(text: str) -> List[Dict[str, str]]:
    rows = []
    lines = text.strip().split('\n')
    header = None
    for line in lines:
        line = line.strip()
        if not line.startswith('|'):
            continue
        cells = [c.strip() for c in line.split('|')[1:-1]]
        if not cells:
            continue
        if header is None:
            header = cells
            continue
        if all(set(c.strip()) <= {'-'} for c in cells):
            continue
        if header and len(cells) == len(header):
            rows.append(dict(zip(header, cells)))
    return rows


def _parse_severity_table(text: str) -> List[SeverityLevel]:
    rows = _parse_markdown_table(text)
    levels = []
    for row in rows:
        level_name = row.get('情节等级', row.get('等级', ''))
        circumstance = row.get('违法情节', row.get('情节', ''))
        penalty = row.get('裁量基准', row.get('处罚', ''))
        if level_name or circumstance or penalty:
            levels.append(SeverityLevel(
                level=level_name.strip(),
                circumstance=circumstance.strip(),
                penalty=penalty.strip(),
            ))
    return levels


def _parse_field_table(text: str) -> Dict[str, str]:
    rows = _parse_markdown_table(text)
    result = {}
    for row in rows:
        keys = [k for k in row.keys() if k and not all(c in '-' for c in k)]
        if len(keys) >= 2:
            key = row[keys[0]].strip().strip('*').strip()
            value = row[keys[1]].strip()
            if key:
                result[key] = value
    return result


def _detect_csv_type(content: str) -> str:
    first_line = content.split('\n', 1)[0].strip()
    if ',' not in first_line:
        return ""
    if '违法行为' in first_line and '违法情节' in first_line and '裁量基准' in first_line:
        return "discretion"
    if '违法事项' in first_line or ('事项' in first_line and '适用条件' in first_line):
        return "list"
    if '序号' in first_line and ',' in first_line:
        return "generic_csv"
    return ""


def _parse_severity_from_csv(circumstance: str, penalty: str) -> List[SeverityLevel]:
    if not circumstance and not penalty:
        return []

    circ_parts = re.split(r'有下列(?:情形|情节严重情形)之一的[：:]\s*', circumstance)
    circ_parts = [p.strip().rstrip('。').strip() for p in circ_parts if p.strip()]

    pen_parts = re.split(r'(?<=。)\s*(?=（1）)', penalty)
    pen_parts = [p.strip() for p in pen_parts if p.strip()]
    if not pen_parts:
        pen_parts = [penalty] if penalty else ['']

    level_names = ['从轻', '一般', '从重', '严重']
    levels = []
    for i in range(max(len(circ_parts), 1)):
        level_name = level_names[i] if i < len(level_names) else f'等级{i + 1}'
        circ = circ_parts[i] if i < len(circ_parts) else ''
        pen = pen_parts[i] if i < len(pen_parts) else ''
        levels.append(SeverityLevel(
            level=level_name,
            circumstance=circ,
            penalty=pen,
        ))
    return levels


def _csv_val(row: Dict, key: str, default: str = '') -> str:
    value = row.get(key, default)
    if isinstance(value, list):
        return '；'.join(str(v) for v in value if v)
    return str(value) if value is not None else default


class ArticleParser:
    """按编/章/节/条的层级结构解析法律法规文档。"""

    # 匹配编/章/节/条的正则（支持 Markdown 标题 ###、加粗 **、纯文本）
    # 关键：节 和 条 的区别 — "第四节"后面是空格+内容，"第四条"后面也是空格+内容
    # 解决：节 通常出现在章之后、条之前；且节的内容较短（几行后就是条）
    # 更精确的匹配：要求"第X节"后面跟的内容中不含"条"字开头
    # 匹配编/章/节/条 — 支持全角空格\u3000、普通空格、Markdown标题标记
    # 关键：条 和 节 的区别 — "第四条" vs "第四节"
    # 解决：先匹配"条"（更具体），"节"只匹配不含"条"的行
    RE_BIAN = re.compile(r'^(?:#{1,3}\s+|[\*\s]*)第([一二三四五六七八九十百千零\d]+)编[\s\u3000]')
    RE_ZHANG = re.compile(r'^(?:#{1,3}\s+|[\*\s]*)第([一二三四五六七八九十百千零\d]+)章[\s\u3000]')
    RE_JIE = re.compile(r'^(?:#{1,3}\s+|[\*\s]*)第([一二三四五六七八九十百千零\d]+)节[\s\u3000](?!.*条)')
    RE_TIAO = re.compile(r'^(?:#{1,3}\s+|[\*\s]*)第([一二三四五六七八九十百千零\d]+)条[\s\u3000]')

    def parse(self, content: str, metadata: dict) -> List[DocumentChunk]:
        lines = content.split('\n')
        chunks: List[DocumentChunk] = []

        current_bian = ""
        current_zhang = ""
        current_jie = ""
        current_article_num = ""
        current_article_lines: List[str] = []

        def flush_article():
            if current_article_num and current_article_lines:
                article_content = '\n'.join(current_article_lines).strip()
                if len(article_content) > 20:  # 过滤空/极短法条
                    # 构建上下文：章节信息 + 法条内容
                    context_parts = []
                    if current_bian:
                        context_parts.append(current_bian)
                    if current_zhang:
                        context_parts.append(current_zhang)
                    if current_jie:
                        context_parts.append(current_jie)
                    # 法条内容前注入章节上下文（用于 Embedding）
                    embedding_text = '\n'.join(context_parts) + '\n' + article_content if context_parts else article_content

                    chunks.append(DocumentChunk(
                        chunk_id=f"{metadata.get('doc_name', 'doc')}_{current_article_num}",
                        source_file=metadata.get('doc_name', ''),
                        source_type="laws",
                        article_number=current_article_num,
                        content=article_content,
                        referenced_laws=_extract_law_refs(article_content),
                        full_text=embedding_text,
                        metadata={
                            "bian": current_bian,
                            "zhang": current_zhang,
                            "jie": current_jie,
                        }
                    ))

        for line in lines:
            line_stripped = line.strip()
            if not line_stripped:
                if current_article_lines:
                    current_article_lines.append("")
                continue

            # 检测「条」— 优先匹配，避免"第四条"被"节"误匹配
            m_tiao = self.RE_TIAO.match(line_stripped)
            if m_tiao:
                flush_article()
                current_article_num = f"第{m_tiao.group(1)}条"
                current_article_lines = [line_stripped]
                continue

            # 检测「编」
            m_bian = self.RE_BIAN.match(line_stripped)
            if m_bian:
                flush_article()
                current_bian = f"第{m_bian.group(1)}编"
                current_zhang = ""   # 新编开始时清除章
                current_jie = ""     # 新编开始时清除节
                current_article_num = ""
                current_article_lines = []
                continue

            # 检测「章」
            m_zhang = self.RE_ZHANG.match(line_stripped)
            if m_zhang:
                flush_article()
                current_zhang = f"第{m_zhang.group(1)}章"
                current_jie = ""     # 新章开始时清除节
                current_article_num = ""
                current_article_lines = []
                continue

            # 检测「节」— 在条之后检查，且排除含"条"的行
            m_jie = self.RE_JIE.match(line_stripped)
            if m_jie:
                flush_article()
                current_jie = f"第{m_jie.group(1)}节"
                current_article_num = ""
                current_article_lines = []
                continue

            # 普通内容行
            if current_article_num:
                current_article_lines.append(line_stripped)

        # 最后一个法条
        flush_article()

        # 兜底：如果没有切出任何法条，整文档作为一个 chunk
        if not chunks:
            return [DocumentChunk(
                chunk_id=f"doc_{metadata.get('doc_id', 'unknown')}",
                source_type="laws",
                content=content,
                full_text=content,
            )]

        return chunks


class ViolationParser:
    def parse(self, content: str, metadata: dict) -> List[DocumentChunk]:
        pattern = re.compile(r'###\s*第(\d+)项')
        sections = _split_by_pattern(content, pattern)
        if not sections:
            return [DocumentChunk(
                chunk_id=f"doc_{metadata.get('doc_id', 'unknown')}",
                source_type="standards",
                category=metadata.get('category', ''),
                content=content,
                full_text=content,
            )]
        chunks = []
        for seq, section_content in sections:
            violation = self._extract_violation_title(section_content)
            legal_basis = self._extract_legal_basis(section_content)
            legal_basis_detail = self._extract_legal_basis_detail(section_content)
            severity_levels = _parse_severity_table(section_content)
            chunks.append(DocumentChunk(
                chunk_id=f"{metadata.get('category', 'std')}_{seq}",
                source_file=metadata.get('doc_name', ''),
                source_type="standards",
                category=metadata.get('category', ''),
                seq_number=int(seq),
                violation=violation,
                legal_basis=legal_basis,
                legal_basis_detail=legal_basis_detail,
                severity_levels=severity_levels if severity_levels else None,
                full_text=section_content,
            ))
        return chunks

    def _extract_violation_title(self, text: str) -> str:
        lines = text.split('\n')
        for line in lines[:5]:
            line = line.strip()
            if line.startswith('###'):
                title = re.sub(r'^###\s*第\d+项\s*', '', line).strip()
                if title:
                    return title
        first_line = lines[0].strip() if lines else ""
        title = re.sub(r'^###\s*', '', first_line).strip()
        title = re.sub(r'^第\d+项\s*', '', title).strip()
        return title

    def _extract_legal_basis(self, text: str) -> str:
        pattern = re.compile(r'\*\*法律依据\*\*[：:]\s*')
        match = pattern.search(text)
        if not match:
            law_refs = _extract_law_refs(text)
            return law_refs[0] if law_refs else ""
        start = match.end()
        end = text.find('\n\n', start)
        if end == -1:
            end = text.find('\n|', start)
        if end == -1:
            end = min(start + 200, len(text))
        basis_text = text[start:end].strip()
        first_ref = re.search(r'《[^》]+》第[一二三四五六七八九十百千零\d]+条', basis_text)
        if first_ref:
            return first_ref.group(0)
        law_refs = _extract_law_refs(basis_text)
        return law_refs[0] if law_refs else basis_text[:100]

    def _extract_legal_basis_detail(self, text: str) -> str:
        pattern = re.compile(r'\*\*法律依据\*\*[：:]\s*')
        match = pattern.search(text)
        if not match:
            return ""
        start = match.end()
        end = text.find('\n|', start)
        if end == -1:
            end = text.find('\n\n', start)
        if end == -1:
            end = min(start + 500, len(text))
        return text[start:end].strip()


class ItemParser:
    def parse(self, content: str, metadata: dict) -> List[DocumentChunk]:
        pattern = re.compile(r'###\s*第(\d+)项')
        sections = _split_by_pattern(content, pattern)
        if not sections:
            return [DocumentChunk(
                chunk_id=f"doc_{metadata.get('doc_id', 'unknown')}",
                source_type="standards",
                list_type=metadata.get('list_type', ''),
                content=content,
                full_text=content,
            )]
        chunks = []
        for seq, section_content in sections:
            fields = _parse_field_table(section_content)
            chunks.append(DocumentChunk(
                chunk_id=f"{metadata.get('list_type', 'list')}_{seq}",
                source_file=metadata.get('doc_name', ''),
                source_type="standards",
                list_type=metadata.get('list_type', ''),
                category=fields.get('管理领域', ''),
                seq_number=int(seq),
                violation=fields.get('违法事项', ''),
                applicable_conditions=fields.get('适用条件', ''),
                violated_provision=fields.get('违反条款', ''),
                penalty_basis=fields.get('处罚依据', ''),
                full_text=section_content,
            ))
        return chunks


class CaseParser:
    def parse(self, content: str, metadata: dict) -> List[DocumentChunk]:
        pattern = re.compile(r'###\s*(.+?案)')
        sections = _split_by_pattern(content, pattern)
        if not sections:
            return [DocumentChunk(
                chunk_id=f"case_{metadata.get('doc_name', 'doc')}_1",
                source_type="cases",
                content=content,
                full_text=content,
            )]
        chunks = []
        for idx, (case_title, case_content) in enumerate(sections):
            case_info = self._extract_case_info(case_content, case_title)
            chunks.append(DocumentChunk(
                chunk_id=case_info.get('case_id', f"case_{metadata.get('doc_name', 'doc')}_{idx + 1}"),
                source_file=metadata.get('doc_name', ''),
                source_type="cases",
                case_type=case_info.get('case_type', '普通案例'),
                case_id=case_info.get('case_id', ''),
                violation_type=case_info.get('violation_type', ''),
                violation_category=case_info.get('violation_category', ''),
                legal_basis_list=case_info.get('legal_basis', []),
                penalty_summary=case_info.get('penalty_summary', ''),
                discretion_level=case_info.get('discretion_level', ''),
                full_text=case_content,
            ))
        return chunks

    def _extract_case_info(self, text: str, title: str) -> dict:
        info = {
            'violation_type': '',
            'legal_basis': _extract_law_refs(text),
            'penalty_summary': '',
        }
        penalty_match = re.search(r'(?:处罚决定|行政处罚|罚款|没收)[：:]\s*(.+?)(?:\n|$)', text)
        if penalty_match:
            info['penalty_summary'] = penalty_match.group(1).strip()[:200]
        type_match = re.search(r'(?:案例类型|案件类型)[：:]\s*(.+?)(?:\n|$)', text)
        if type_match:
            info['case_type'] = type_match.group(1).strip()
        return info


class MarkdownHierarchyParser:
    """按 Markdown #/##/### 层级结构解析文档，适用于已格式化的法律法规。

    新格式示例：
        ---
        title: "食品安全法"
        region: "全国"
        category: "市场法规"
        status: "有效"
        ---
        # 食品安全法              ← # 文档标题（1级）
        ## 第一章 总则            ← ## 章/编/节（2级）
        ### 第一条                ← ### 条（3级，切分粒度）
        为了保证食品安全...
    """

    RE_H1 = re.compile(r'^#\s+(.+)$', re.MULTILINE)
    RE_H2 = re.compile(r'^##\s+(.+)$', re.MULTILINE)
    RE_H3 = re.compile(r'^###\s+(.+)$', re.MULTILINE)

    def _extract_frontmatter(self, content: str) -> tuple:
        """提取 YAML frontmatter，返回 (frontmatter_dict, remaining_content)"""
        if not content.startswith('---'):
            return {}, content

        end_idx = content.find('---', 3)
        if end_idx == -1:
            return {}, content

        yaml_text = content[3:end_idx].strip()
        body = content[end_idx + 3:].strip()

        if not yaml_text:
            return {}, body

        try:
            import yaml
            fm = yaml.safe_load(yaml_text)
            return fm if isinstance(fm, dict) else {}, body
        except Exception:
            return {}, body

    def parse(self, content: str, metadata: dict) -> List[DocumentChunk]:
        frontmatter, body = self._extract_frontmatter(content)

        doc_title = frontmatter.get('title', '') or metadata.get('doc_name', '')
        region = frontmatter.get('region', metadata.get('region', ''))
        category = frontmatter.get('category', metadata.get('category', ''))
        status = frontmatter.get('status', '有效')
        effective_date = str(frontmatter.get('effective_date', ''))
        source_val = frontmatter.get('source', '')

        lines = body.split('\n')
        chunks: List[DocumentChunk] = []

        current_h1 = ""
        current_h2 = ""
        current_h3 = ""
        current_h3_lines: List[str] = []
        h3_counter = 0

        def flush_h3():
            nonlocal h3_counter
            if current_h3 and current_h3_lines:
                article_content = '\n'.join(current_h3_lines).strip()
                if len(article_content) > 15:
                    h3_counter += 1
                    context_parts = []
                    if current_h1:
                        context_parts.append(current_h1)
                    if current_h2:
                        context_parts.append(current_h2)

                    embedding_text = '\n'.join(context_parts) + '\n' + current_h3 + '\n' + article_content if context_parts else current_h3 + '\n' + article_content

                    chunks.append(DocumentChunk(
                        chunk_id=f"{doc_title}_{current_h3}_{h3_counter}",
                        source_file=metadata.get('doc_name', ''),
                        source_type="laws",
                        article_number=current_h3,
                        content=article_content,
                        referenced_laws=_extract_law_refs(article_content),
                        full_text=embedding_text,
                        metadata={
                            "h1": current_h1,
                            "h2": current_h2,
                            "title": doc_title,
                            "region": region,
                            "category": category,
                            "status": status,
                            "effective_date": effective_date,
                            **{k: v for k, v in frontmatter.items() if k not in ('title', 'region', 'category', 'status', 'effective_date')},
                        }
                    ))

        for line in lines:
            stripped = line.strip()
            if not stripped:
                if current_h3_lines:
                    current_h3_lines.append("")
                continue

            m_h3 = self.RE_H3.match(stripped)
            if m_h3:
                flush_h3()
                current_h3 = m_h3.group(1).strip()
                current_h3_lines = [stripped]
                continue

            m_h2 = self.RE_H2.match(stripped)
            if m_h2:
                flush_h3()
                current_h2 = m_h2.group(1).strip()
                current_h3 = ""
                current_h3_lines = []
                continue

            m_h1 = self.RE_H1.match(stripped)
            if m_h1:
                flush_h3()
                current_h1 = m_h1.group(1).strip()
                current_h2 = ""
                current_h3 = ""
                current_h3_lines = []
                continue

            if current_h3:
                current_h3_lines.append(stripped)

        flush_h3()

        if not chunks:
            return [DocumentChunk(
                chunk_id=f"doc_{metadata.get('doc_id', 'unknown')}",
                source_file=metadata.get('doc_name', ''),
                source_type="laws",
                content=body,
                full_text=body,
                metadata={
                    "title": doc_title,
                    "region": region,
                    "category": category,
                    "status": status,
                    **frontmatter,
                },
            )]

        return chunks


class WholeDocParser:
    def parse(self, content: str, metadata: dict) -> List[DocumentChunk]:
        return [DocumentChunk(
            chunk_id=f"doc_{metadata.get('doc_id', 'unknown')}",
            source_file=metadata.get('doc_name', ''),
            source_type="other",
            content=content,
            full_text=content,
        )]


class DiscretionCsvParser:
    def parse(self, content: str, metadata: dict) -> List[DocumentChunk]:
        rows = self._read_csv(content)
        if not rows:
            return [DocumentChunk(
                chunk_id=f"doc_{metadata.get('doc_id', 'unknown')}",
                source_type="standards",
                content=content,
                full_text=content,
            )]

        chunks = []
        for row in rows:
            seq = _csv_val(row, '序号', '0')
            violation = _csv_val(row, '违法行为')
            legal_basis = _csv_val(row, '法律依据')
            circumstance = _csv_val(row, '违法情节')
            penalty = _csv_val(row, '裁量基准')
            category_name = _csv_val(row, '分类')

            severity_levels = _parse_severity_from_csv(circumstance, penalty)
            full_text = self._build_full_text(row)

            chunks.append(DocumentChunk(
                chunk_id=f"{metadata.get('doc_name', 'std')}_{seq}",
                source_file=metadata.get('doc_name', ''),
                source_type="standards",
                category=category_name,
                seq_number=int(seq) if seq.isdigit() else 0,
                violation=violation,
                legal_basis=self._extract_first_law_ref(legal_basis),
                legal_basis_detail=legal_basis,
                severity_levels=severity_levels if severity_levels else None,
                full_text=full_text,
            ))
        return chunks

    def _read_csv(self, content: str) -> List[Dict]:
        try:
            reader = csv.DictReader(io.StringIO(content))
            return list(reader)
        except Exception:
            return []

    def _extract_first_law_ref(self, text: str) -> str:
        match = re.search(r'《[^》]+》第[一二三四五六七八九十百千零\d]+条', text)
        if match:
            return match.group(0)
        refs = _extract_law_refs(text)
        return refs[0] if refs else text[:100]

    def _build_full_text(self, row: Dict) -> str:
        parts = []
        field_order = ['分类', '序号', '违法行为', '法律依据', '违法情节', '裁量基准']
        for key in field_order:
            value = _csv_val(row, key)
            if value and value.strip():
                parts.append(f"{key}：{value.strip()}")
        return '\n'.join(parts)


class ListCsvParser:
    def parse(self, content: str, metadata: dict) -> List[DocumentChunk]:
        rows = self._read_csv(content)
        if not rows:
            return [DocumentChunk(
                chunk_id=f"doc_{metadata.get('doc_id', 'unknown')}",
                source_type="standards",
                content=content,
                full_text=content,
            )]

        chunks = []
        for row in rows:
            seq = _csv_val(row, '序号', '0')
            headers = list(row.keys())

            if '类别' in headers and '事项' in headers:
                chunk = self._parse_minor_list_row(row, seq, metadata)
            elif '管理领域' in headers and '违法事项' in headers:
                chunk = self._parse_admin_list_row(row, seq, metadata)
            else:
                chunk = self._parse_generic_row(row, seq, metadata)
            chunks.append(chunk)
        return chunks

    def _read_csv(self, content: str) -> List[Dict]:
        try:
            reader = csv.DictReader(io.StringIO(content))
            return list(reader)
        except Exception:
            return []

    def _parse_minor_list_row(self, row: Dict, seq: str, metadata: dict) -> DocumentChunk:
        return DocumentChunk(
            chunk_id=f"minor_{metadata.get('doc_name', 'list')}_{seq}",
            source_file=metadata.get('doc_name', ''),
            source_type="standards",
            list_type="轻微违法免罚清单",
            category=_csv_val(row, '类别'),
            seq_number=int(seq) if seq.isdigit() else 0,
            violation=_csv_val(row, '事项'),
            applicable_conditions=_csv_val(row, '适用条件'),
            violated_provision=_csv_val(row, '违反条款'),
            penalty_basis=_csv_val(row, '处罚依据'),
            full_text=self._build_full_text(row),
        )

    def _parse_admin_list_row(self, row: Dict, seq: str, metadata: dict) -> DocumentChunk:
        headers = list(row.keys())
        if '从轻行政处罚的情形' in headers:
            list_type = "从轻处罚清单"
            conditions = _csv_val(row, '从轻行政处罚的情形')
            basis = _csv_val(row, '从轻行政处罚的依据')
        elif '减轻行政处罚的情形' in headers:
            list_type = "减轻处罚清单"
            conditions = _csv_val(row, '减轻行政处罚的情形')
            basis = _csv_val(row, '减轻行政处罚的依据')
        elif '免予行政强制措施的情形' in headers:
            list_type = "免于强制措施清单"
            conditions = _csv_val(row, '免予行政强制措施的情形')
            basis = _csv_val(row, '免予行政强制措施的依据')
        else:
            list_type = "清单"
            conditions = ''
            basis = ''

        return DocumentChunk(
            chunk_id=f"{list_type}_{seq}",
            source_file=metadata.get('doc_name', ''),
            source_type="standards",
            list_type=list_type,
            category=_csv_val(row, '管理领域'),
            seq_number=int(seq) if seq.isdigit() else 0,
            violation=_csv_val(row, '违法事项'),
            applicable_conditions=conditions,
            legal_basis=_csv_val(row, '设定依据'),
            legal_basis_detail=basis,
            penalty_basis=_csv_val(row, '后续监管及裁量幅度') or _csv_val(row, '后续监管措施'),
            full_text=self._build_full_text(row),
        )

    def _parse_generic_row(self, row: Dict, seq: str, metadata: dict) -> DocumentChunk:
        return DocumentChunk(
            chunk_id=f"csv_{metadata.get('doc_name', 'doc')}_{seq}",
            source_file=metadata.get('doc_name', ''),
            source_type="standards",
            seq_number=int(seq) if seq.isdigit() else 0,
            full_text=self._build_full_text(row),
        )

    def _build_full_text(self, row: Dict) -> str:
        parts = []
        for key, value in row.items():
            val = _csv_val(row, key) if key in row else ''
            if val and val.strip():
                parts.append(f"{key}：{val.strip()}")
        return '\n'.join(parts)


def csv_to_markdown(content: str) -> str:
    try:
        reader = csv.DictReader(io.StringIO(content))
        rows = list(reader)
        if not rows:
            return content

        md_parts = []
        for i, row in enumerate(rows):
            seq = row.get('序号', str(i + 1))
            title_field = row.get('违法行为', row.get('违法事项', row.get('事项', f'条目{seq}')))
            md_parts.append(f"### 第{seq}项 {title_field}")
            md_parts.append("")
            for key, value in row.items():
                if key in ('序号', '违法行为', '违法事项', '事项'):
                    continue
                if value and value.strip():
                    md_parts.append(f"**{key}**：{value.strip()}")
                    md_parts.append("")
            md_parts.append("---")
            md_parts.append("")
        return '\n'.join(md_parts)
    except Exception:
        return content


class ChunkEngine:
    def __init__(self):
        self._parsers = {
            "by_article": ArticleParser(),
            "by_section": MarkdownHierarchyParser(),
            "by_violation": ViolationParser(),
            "by_item": ItemParser(),
            "by_case": CaseParser(),
            "whole_doc": WholeDocParser(),
        }
        self._csv_parsers = {
            "by_violation": DiscretionCsvParser(),
            "by_item": ListCsvParser(),
        }

    def chunk(self, content: str, strategy: str, category: str, metadata: dict = None) -> ChunkResult:
        metadata = metadata or {}
        if strategy == "auto":
            strategy = self.auto_detect_strategy(content, category)

        csv_type = _detect_csv_type(content)
        if csv_type and strategy in self._csv_parsers:
            parser = self._csv_parsers[strategy]
        else:
            parser = self._parsers.get(strategy, self._parsers["whole_doc"])

        chunks = parser.parse(content, metadata)

        display_name = self._get_strategy_display_name(strategy)
        if csv_type:
            display_name += "(CSV)"

        return ChunkResult(
            chunks=chunks,
            strategy_used=strategy,
            total_chunks=len(chunks),
            strategy_display_name=display_name,
        )

    def preview(self, content: str, strategy: str, category: str, metadata: dict = None) -> ChunkPreview:
        metadata = metadata or {}
        if strategy == "auto":
            strategy = self.auto_detect_strategy(content, category)

        result = self.chunk(content, strategy, category, metadata)

        preview_chunks = []
        for c in result.chunks:
            item = {"chunk_id": c.chunk_id}
            if c.source_type == "laws":
                item["article_number"] = c.article_number
                item["content_preview"] = c.content[:100] if c.content else ""
                item["referenced_laws"] = c.referenced_laws or []
            elif c.source_type == "standards":
                item["violation"] = c.violation
                item["legal_basis"] = c.legal_basis
                if c.severity_levels:
                    item["severity_level_count"] = len(c.severity_levels)
                elif c.applicable_conditions:
                    item["applicable_conditions_preview"] = c.applicable_conditions[:100]
            elif c.source_type == "cases":
                item["case_type"] = c.case_type
                item["violation_type"] = c.violation_type
                item["penalty_summary"] = c.penalty_summary
            else:
                item["content_preview"] = c.content[:100] if c.content else ""
            preview_chunks.append(item)

        return ChunkPreview(
            chunks=preview_chunks,
            strategy_used=result.strategy_used,
            total_chunks=result.total_chunks,
            strategy_display_name=result.strategy_display_name,
            estimated_qdrant_points=result.total_chunks,
        )

    def auto_detect_strategy(self, content: str, category: str) -> str:
        csv_type = _detect_csv_type(content)
        if csv_type == "discretion":
            return "by_violation"
        elif csv_type in ("list", "generic_csv"):
            return "by_item"

        if category == "laws":
            return "by_article"
        elif category == "cases":
            return "by_case"
        elif category == "standards":
            if re.search(r'###\s*第\d+项', content):
                if re.search(r'\|.*情节等级.*\|.*违法情节.*\|.*裁量基准.*\|', content):
                    return "by_violation"
                elif re.search(r'\|.*\*{0,2}字段\*{0,2}.*\|.*\*{0,2}内容\*{0,2}.*\|', content):
                    return "by_item"
                if re.search(r'\|.*违法事项.*\|', content):
                    return "by_item"
                return "by_violation"
            return "by_violation"
        else:
            if len(content) < 2000:
                return "whole_doc"
            if re.search(r'\*\*第[一二三四五六七八九十百千零\d]+条\*\*', content):
                return "by_article"
            return "whole_doc"

    def _get_strategy_display_name(self, strategy: str) -> str:
        for s in STRATEGY_LIST:
            if s["key"] == strategy:
                return s["name"]
        return strategy


chunk_engine = ChunkEngine()
