"""Synthesize a Salesforce technical design document from requirements + design."""

from __future__ import annotations

import html
import re
from dataclasses import dataclass, field
from typing import List, Optional, Sequence

from packages.artifacts.design import DesignArtifact, MetadataPlanItem
from packages.artifacts.requirements import RequirementsArtifact


@dataclass
class TechDocTable:
    headers: List[str]
    rows: List[List[str]]


@dataclass
class TechDocBlock:
    heading: Optional[str] = None
    paragraphs: List[str] = field(default_factory=list)
    bullets: List[str] = field(default_factory=list)
    code: Optional[str] = None
    table: Optional[TechDocTable] = None


@dataclass
class TechDocSection:
    number: int
    title: str
    blocks: List[TechDocBlock] = field(default_factory=list)


@dataclass
class TechnicalDesignDocument:
    title: str
    sections: List[TechDocSection]


def technical_design_title(
    summary: str,
    *,
    jira_key: str = "",
    max_len: int = 180,
) -> str:
    feature = (summary or "").strip() or "Design"
    # Drop a trailing "(KEY)" if the LLM already appended one.
    if jira_key:
        feature = re.sub(rf"\s*\({re.escape(jira_key)}\)\s*$", "", feature, flags=re.I).strip() or "Design"
    if re.match(r"(?i)^technical design\b", feature):
        title = feature
    else:
        title = f"Technical Design — {feature}"
    if jira_key and f"({jira_key})" not in title:
        suffix = f" ({jira_key})"
        # Keep jira key visible even when truncating.
        budget = max_len - len(suffix)
        if budget < 20:
            return f"Technical Design ({jira_key})"[:max_len]
        if len(title) > budget:
            title = title[: budget - 1].rstrip() + "…"
        title = f"{title}{suffix}"
    elif len(title) > max_len:
        title = title[: max_len - 1].rstrip() + "…"
    return title


def synthesize_technical_document(
    requirements: RequirementsArtifact,
    design: DesignArtifact,
) -> TechnicalDesignDocument:
    fields = [i for i in design.metadata_plan if i.metadata_type == "CustomField"]
    validations = [i for i in design.metadata_plan if i.metadata_type == "ValidationRule"]
    other = [
        i
        for i in design.metadata_plan
        if i.metadata_type not in {"CustomField", "ValidationRule"}
    ]
    blob = _blob(requirements, design)
    primary = fields[0] if fields else None
    obj = (primary.object if primary and primary.object else None) or _guess_object(blob)
    label = _field_label(primary, blob) if primary else (design.summary or requirements.summary)
    picklist_values = _picklist_values(primary, blob) if primary else []
    default_value = _default_value(primary, blob, picklist_values) if primary else None
    is_picklist = bool(picklist_values) or _mentions_picklist(primary, blob)

    in_scope = [a.strip() for a in requirements.acceptance_criteria if a.strip()]
    if not in_scope:
        in_scope = [_plan_scope_line(i) for i in design.metadata_plan]
    out_of_scope = [x.strip() for x in requirements.out_of_scope_items if x.strip()]
    if not out_of_scope:
        out_of_scope = [
            "Documentation and training.",
            "Production deployment.",
        ]

    layouts_mentioned = bool(re.search(r"page layout", blob, re.I))
    type_value = _required_type_value(blob)

    sections = [
        _overview_section(requirements, design, obj, label, type_value, is_picklist),
        _scope_section(in_scope, out_of_scope),
        _data_model_section(fields, other, obj, label, picklist_values, default_value, is_picklist, type_value),
        _picklist_section(primary, picklist_values, default_value, is_picklist),
        _layout_section(label, primary, layouts_mentioned, obj),
        _validation_section(validations, primary, label, picklist_values, type_value, blob),
        _processing_section(obj, label, default_value, type_value, validations, picklist_values),
        _security_section(out_of_scope, validations),
        _deployment_section(fields, validations, other, obj, label, picklist_values, default_value, layouts_mentioned),
        _testing_section(in_scope, obj, label, picklist_values, default_value, type_value, layouts_mentioned),
        _traceability_section(in_scope, out_of_scope, fields, validations, layouts_mentioned),
        _summary_section(design, fields, validations, other, obj, label, type_value),
    ]
    return TechnicalDesignDocument(
        title=technical_design_title(
            design.summary or requirements.summary,
            jira_key=design.jira_key or requirements.jira_key,
        ),
        sections=sections,
    )


def technical_document_storage_html(doc: TechnicalDesignDocument) -> str:
    parts: List[str] = [f"<h1>{_esc(doc.title)}</h1>"]
    for section in doc.sections:
        parts.append(f"<h2>{_esc(f'{section.number}. {section.title}')}</h2>")
        for block in section.blocks:
            if block.heading:
                parts.append(f"<h3>{_esc(block.heading)}</h3>")
            for paragraph in block.paragraphs:
                parts.append(f"<p>{_esc(paragraph)}</p>")
            if block.bullets:
                parts.append("<ul>")
                for item in block.bullets:
                    parts.append(f"<li>{_esc(item)}</li>")
                parts.append("</ul>")
            if block.code:
                parts.append(f"<pre>{_esc(block.code)}</pre>")
            if block.table:
                parts.append(_table_html(block.table))
    return "\n".join(parts)


def _overview_section(
    requirements: RequirementsArtifact,
    design: DesignArtifact,
    obj: str,
    label: str,
    type_value: Optional[str],
    is_picklist: bool,
) -> TechDocSection:
    control = "a custom picklist" if is_picklist else "a custom field"
    values = _picklist_values_from_design(design, requirements)
    if values:
        intro = (
            f"This change introduces a standardized {label} classification for Salesforce "
            f"{obj} records. Users will use {control} to classify {obj} records as "
            f"{_join_or(values)}."
        )
    else:
        intro = (
            f"This change introduces a standardized {label} classification for Salesforce "
            f"{obj} records. Users will use {control} on {obj} records."
        )
    paragraphs = [
        intro,
        (design.approach or requirements.summary).strip(),
    ]
    if type_value:
        paragraphs.append(
            f"{type_value} {obj} records must have {label} before they can be saved. "
            f"For {obj.lower()}s that are not {type_value}s, the field remains optional."
        )
    # Drop duplicate/empty approach if it duplicates the first paragraph
    cleaned = [p for p in paragraphs if p]
    unique: List[str] = []
    for p in cleaned:
        if p not in unique:
            unique.append(p)
    return TechDocSection(1, "Overview", [TechDocBlock(paragraphs=unique)])


def _picklist_values_from_design(
    design: DesignArtifact, requirements: RequirementsArtifact
) -> List[str]:
    blob = _blob(requirements, design)
    fields = [i for i in design.metadata_plan if i.metadata_type == "CustomField"]
    return _picklist_values(fields[0] if fields else None, blob)


def _scope_section(in_scope: Sequence[str], out_of_scope: Sequence[str]) -> TechDocSection:
    return TechDocSection(
        2,
        "Scope",
        [
            TechDocBlock(heading="In Scope", bullets=list(in_scope) or ["See metadata plan."]),
            TechDocBlock(heading="Out of Scope", bullets=list(out_of_scope)),
        ],
    )


def _data_model_section(
    fields: List[MetadataPlanItem],
    other: List[MetadataPlanItem],
    obj: str,
    label: str,
    values: List[str],
    default_value: Optional[str],
    is_picklist: bool,
    type_value: Optional[str],
) -> TechDocSection:
    blocks: List[TechDocBlock] = []
    if not fields and not other:
        blocks.append(TechDocBlock(paragraphs=["No data-model metadata is included in this design."]))
        return TechDocSection(3, "Data Model", blocks)

    for item in fields:
        field_label = _field_label(item, "") or label
        data_type = "Picklist" if is_picklist or _mentions_picklist(item, item.notes or "") else _field_data_type(item)
        rows = [
            ["Object", item.object or obj],
            ["Field API Name", item.api_name],
            ["Field Label", field_label],
            ["Data Type", data_type],
        ]
        if values:
            rows.append(["Values", ", ".join(values)])
        if default_value:
            rows.append(["Default Value", default_value])
        rows.extend(
            [
                ["Required at Field Definition", "No" if type_value else "No"],
                ["Controlling Field", "None"],
            ]
        )
        heading = f"{item.object or obj} Field" if item.object or obj else item.api_name
        blocks.append(
            TechDocBlock(
                heading=heading,
                table=TechDocTable(headers=["Property", "Configuration"], rows=rows),
            )
        )
        if type_value:
            blocks.append(
                TechDocBlock(
                    paragraphs=[
                        f"The field should not be configured as universally required at the field-definition "
                        f"level because the requirement only mandates the value for {obj}s whose Type is {type_value}."
                    ]
                )
            )

    for item in other:
        blocks.append(
            TechDocBlock(
                heading=f"{item.metadata_type} {item.api_name}",
                table=TechDocTable(
                    headers=["Property", "Configuration"],
                    rows=[
                        ["Metadata Type", item.metadata_type],
                        ["API Name", item.api_name],
                        ["Action", item.action],
                        ["Object", item.object or "—"],
                        ["Notes", item.notes or "—"],
                    ],
                ),
            )
        )
    return TechDocSection(3, "Data Model", blocks)


def _picklist_section(
    primary: Optional[MetadataPlanItem],
    values: List[str],
    default_value: Optional[str],
    is_picklist: bool,
) -> TechDocSection:
    if not primary or not (is_picklist or values):
        return TechDocSection(
            4,
            "Picklist Configuration",
            [TechDocBlock(paragraphs=["No picklist fields are included in this design."])],
        )
    rows = []
    for value in values or ["(values from requirements)"]:
        rows.append([value, "Yes", "Yes" if default_value and value.lower() == default_value.lower() else "No"])
    blocks = [
        TechDocBlock(
            paragraphs=[f"Configure {primary.api_name} with exactly these values:"],
            table=TechDocTable(headers=["Value", "Active", "Default"], rows=rows),
        )
    ]
    if default_value:
        blocks.append(
            TechDocBlock(
                paragraphs=[
                    f"The default value ensures that newly created records receive {default_value} "
                    "unless the user selects another value."
                ]
            )
        )
    return TechDocSection(4, "Picklist Configuration", blocks)


def _layout_section(
    label: str,
    primary: Optional[MetadataPlanItem],
    layouts_mentioned: bool,
    obj: str,
) -> TechDocSection:
    api = primary.api_name if primary else label
    if not layouts_mentioned:
        return TechDocSection(
            5,
            "Page Layout Configuration",
            [
                TechDocBlock(
                    paragraphs=[
                        f"No page layout updates are specified. Expose {label} according to the "
                        f"organization's existing {obj} layout standards if the field must be visible."
                    ]
                )
            ],
        )
    return TechDocSection(
        5,
        "Page Layout Configuration",
        [
            TechDocBlock(
                paragraphs=[
                    f"Add {label} ({api}) to all {obj} page layouts.",
                    f"Recommended placement is within the primary {obj} details/information section "
                    "alongside the existing classification fields, such as Type, where practical.",
                    "The field should remain editable on the layouts unless existing organizational "
                    "configuration dictates otherwise.",
                ]
            )
        ],
    )


def _validation_section(
    validations: List[MetadataPlanItem],
    primary: Optional[MetadataPlanItem],
    label: str,
    values: List[str],
    type_value: Optional[str],
    blob: str,
) -> TechDocSection:
    if not validations:
        return TechDocSection(
            6,
            "Validation Rule",
            [TechDocBlock(paragraphs=["No validation rules are included in this design."])],
        )
    blocks: List[TechDocBlock] = []
    field_api = primary.api_name if primary else _first_custom_field_api(blob)
    for rule in validations:
        purpose = (
            rule.notes
            or f"Prevent a {label} from being saved when the business rule is not satisfied."
        )
        if type_value and field_api:
            purpose = (
                f"Prevent an {rule.object or 'record'} from being saved when its Type is "
                f"{type_value} and {field_api} is blank."
            )
        formula = _validation_formula(rule, field_api, type_value)
        error_message = _validation_error_message(rule, label, type_value)
        blocks.extend(
            [
                TechDocBlock(heading="Rule Name", paragraphs=[rule.api_name]),
                TechDocBlock(heading="Purpose", paragraphs=[purpose]),
                TechDocBlock(heading="Formula", code=formula),
                TechDocBlock(heading="Error Message", paragraphs=[error_message]),
                TechDocBlock(heading="Error Location", paragraphs=[f"{label} field."]),
            ]
        )
        eval_rows = _evaluation_rows(type_value, values, label)
        if eval_rows:
            type_header = f"{rule.object or 'Record'} Type" if type_value else "Condition"
            blocks.append(
                TechDocBlock(
                    heading="Evaluation Behavior",
                    table=TechDocTable(
                        headers=[type_header, label, "Result"],
                        rows=eval_rows,
                    ),
                )
            )
        blocks.append(
            TechDocBlock(
                paragraphs=[
                    "The validation rule must be active and configured without user/profile-specific "
                    "exceptions so that it applies to all users."
                ]
            )
        )
    return TechDocSection(6, "Validation Rule", blocks)


def _processing_section(
    obj: str,
    label: str,
    default_value: Optional[str],
    type_value: Optional[str],
    validations: List[MetadataPlanItem],
    values: List[str],
) -> TechDocSection:
    new_account = [
        f"User creates {_a(obj)}.",
    ]
    if default_value:
        new_account.append(f"{label} defaults to {default_value}.")
        if values:
            new_account.append(f"User may change the value to {_join_or(values)}.")
        else:
            new_account.append("User may change the value to another allowed picklist value.")
    else:
        new_account.append(f"User may populate {label}.")
    if type_value and validations:
        new_account.append(
            f"If Type = {type_value}, the populated {label} satisfies the validation rule."
        )
        new_account.append(
            f"If {label} is blank for {_a(type_value)}, save is blocked."
        )
    existing = [
        f"Existing {obj}s receive the new field according to the configured field/default behavior.",
    ]
    if type_value and validations:
        existing.append(
            f"When {_a(obj)} is saved with Type = {type_value}, the validation rule evaluates {label}."
        )
        existing.append(f"A blank {label} prevents the save.")
    blocks = [
        TechDocBlock(heading=f"New {obj}", bullets=new_account),
        TechDocBlock(heading=f"Existing {obj}", bullets=existing),
        TechDocBlock(
            paragraphs=["No separate automation is required for enforcing the requirement."]
        ),
    ]
    return TechDocSection(7, "Processing Behavior", blocks)


def _security_section(out_of_scope: Sequence[str], validations: List[MetadataPlanItem]) -> TechDocSection:
    oos = " ".join(out_of_scope).lower()
    paragraphs = []
    if "permission" in oos or "sharing" in oos:
        paragraphs.append(
            "No permission sets or sharing rules are included in this implementation."
        )
    else:
        paragraphs.append(
            "Field-level security should follow the organization's existing object-field access model."
        )
    if validations:
        paragraphs.append(
            "The validation rule is intended to apply to all users. Field-level security should follow "
            "the organization's existing field access model; no new permission-set changes are part of "
            "this requirement unless listed in scope."
        )
    return TechDocSection(8, "Security and Access", [TechDocBlock(paragraphs=paragraphs)])


def _deployment_section(
    fields: List[MetadataPlanItem],
    validations: List[MetadataPlanItem],
    other: List[MetadataPlanItem],
    obj: str,
    label: str,
    values: List[str],
    default_value: Optional[str],
    layouts_mentioned: bool,
) -> TechDocSection:
    blocks = [
        TechDocBlock(
            paragraphs=["The implementation should contain the following metadata components:"]
        )
    ]
    for item in fields:
        bullets = [
            f"Object: {item.object or obj}",
            f"Field: {item.api_name}",
            f"Type: {'Picklist' if values else 'Custom Field'}",
            f"Label: {_field_label(item, '') or label}",
        ]
        if values:
            bullets.append(f"Values: {', '.join(values)}")
        if default_value:
            bullets.append(f"Default: {default_value}")
        blocks.append(TechDocBlock(heading="Custom Field", bullets=bullets))
    if layouts_mentioned:
        api = fields[0].api_name if fields else label
        blocks.append(
            TechDocBlock(
                heading="Page Layout Updates",
                bullets=[f"Add {api} to every {obj} page layout."],
            )
        )
    for rule in validations:
        blocks.append(
            TechDocBlock(
                heading="Validation Rule",
                bullets=[
                    f"Name: {rule.api_name}",
                    "Active: Yes",
                    "Formula as specified above.",
                ],
            )
        )
    for item in other:
        blocks.append(
            TechDocBlock(
                heading=item.metadata_type,
                bullets=[
                    f"API Name: {item.api_name}",
                    f"Action: {item.action}",
                    f"Object: {item.object or '—'}",
                ],
            )
        )
    return TechDocSection(9, "Deployment Components", blocks)


def _testing_section(
    in_scope: Sequence[str],
    obj: str,
    label: str,
    values: List[str],
    default_value: Optional[str],
    type_value: Optional[str],
    layouts_mentioned: bool,
) -> TechDocSection:
    functional: List[str] = []
    if default_value:
        functional.append(
            f"Create {_a(obj)} without specifying {label} and verify the default is {default_value}."
        )
    if type_value:
        for value in values:
            functional.append(
                f"Create {_a(type_value)} {obj} with {value} and verify save succeeds."
            )
        functional.append(
            f"Attempt to save a {type_value} {obj} with a blank {label} and verify the save is blocked."
        )
        functional.append(
            f"Create/save a non-{type_value} {obj} with a blank {label} and verify save succeeds."
        )
        functional.append(f"Verify the validation error is displayed on the {label} field.")
    if layouts_mentioned:
        functional.append(f"Verify {label} is visible on every {obj} page layout.")
    if not functional:
        functional = [f"Verify: {ac}" for ac in in_scope] or [
            "Verify the solution behaves as specified in the acceptance criteria."
        ]

    regression = [
        f"Verify existing {obj} creation and editing continues to work"
        + (f" for non-{type_value} types." if type_value else "."),
    ]
    if type_value:
        regression.append(
            f"Verify changing an existing {obj}'s Type to {type_value} requires {label} to be populated."
        )
        regression.append(
            f"Verify changing a {type_value} {obj}'s Type away from {type_value} allows {label} to remain blank."
        )
        regression.append("Verify the validation rule cannot be bypassed based on user identity.")
    return TechDocSection(
        10,
        "Testing Strategy",
        [
            TechDocBlock(heading="Functional Tests", bullets=functional),
            TechDocBlock(heading="Regression Tests", bullets=regression),
        ],
    )


def _traceability_section(
    in_scope: Sequence[str],
    out_of_scope: Sequence[str],
    fields: List[MetadataPlanItem],
    validations: List[MetadataPlanItem],
    layouts_mentioned: bool,
) -> TechDocSection:
    rows: List[List[str]] = []
    for ac in in_scope:
        rows.append([ac, _trace_implementation(ac, fields, validations, layouts_mentioned)])
    for item in out_of_scope:
        rows.append([item, "Not included"])
    return TechDocSection(
        11,
        "Acceptance Criteria Traceability",
        [
            TechDocBlock(
                table=TechDocTable(
                    headers=["Acceptance Criterion", "Design Implementation"],
                    rows=rows or [["(none listed)", "—"]],
                )
            )
        ],
    )


def _summary_section(
    design: DesignArtifact,
    fields: List[MetadataPlanItem],
    validations: List[MetadataPlanItem],
    other: List[MetadataPlanItem],
    obj: str,
    label: str,
    type_value: Optional[str],
) -> TechDocSection:
    uses_apex = any(i.metadata_type in {"ApexClass", "ApexTrigger", "Flow"} for i in other)
    if uses_apex:
        summary = (
            f"{design.approach} The design includes automation metadata in addition to configuration."
        )
    else:
        type_bit = f" specifically for {type_value} {obj.lower()}s" if type_value else ""
        field_bit = (
            f"A custom {obj} picklist provides the controlled classification values"
            if fields
            else "Allowlisted Salesforce metadata implements the approved plan"
        )
        vr_bit = (
            f", page-layout updates expose the field to users, and a validation rule enforces "
            f"the business requirement{type_bit}"
            if validations
            else ""
        )
        summary = (
            f"The solution uses standard Salesforce metadata with no Apex or Flow required. "
            f"{field_bit}{vr_bit}."
        )
    closing = (
        "This design is limited to configuration and validation and does not introduce additional "
        "automation, security configuration, documentation, training, or production deployment activities."
    )
    if design.risks_or_escalations:
        closing = closing + " Risks / escalations: " + "; ".join(design.risks_or_escalations) + "."
    return TechDocSection(
        12,
        "Implementation Summary",
        [TechDocBlock(paragraphs=[summary, closing])],
    )


def _trace_implementation(
    ac: str,
    fields: List[MetadataPlanItem],
    validations: List[MetadataPlanItem],
    layouts_mentioned: bool,
) -> str:
    text = ac.lower()
    if fields and fields[0].api_name.lower() in text:
        obj = fields[0].object or "object"
        return f"Custom {obj} picklist field" if "picklist" in text else f"Custom {obj} field"
    if "label" in text:
        return "Field label configured accordingly"
    if "default" in text:
        return "Default value configured on the picklist"
    if "layout" in text:
        return "Layout updates included for every layout" if layouts_mentioned else "Layout guidance captured"
    if "all users" in text:
        return "Active validation rule with no user-specific bypass"
    if validations and validations[0].api_name.lower() in text:
        return validations[0].api_name
    if "validation" in text or "required" in text:
        if validations:
            return validations[0].api_name
        return "Validation rule"
    if "blank" in text or "block" in text or ("customer" in text and "must" in text):
        return "Validation formula checks Type and blank field"
    if "value" in text or "enterprise" in text or "picklist" in text:
        return "Active picklist values"
    if "out of scope" in text or "not included" in text:
        return "Not included"
    return "Covered in the metadata plan"


def _a(noun: str) -> str:
    article = "an" if noun[:1].lower() in "aeiou" else "a"
    return f"{article} {noun}"


def _blob(requirements: RequirementsArtifact, design: DesignArtifact) -> str:
    parts = [
        requirements.summary,
        design.summary,
        design.approach,
        design.notes or "",
        " ".join(requirements.acceptance_criteria),
        " ".join(item.notes or "" for item in design.metadata_plan),
    ]
    return "\n".join(parts)


def _guess_object(blob: str) -> str:
    for candidate in ("Account", "Contact", "Opportunity", "Lead", "Case"):
        if re.search(rf"\b{candidate}\b", blob, re.I):
            return candidate
    return "Account"


def _humanize_api(api_name: str) -> str:
    return api_name.replace("__c", "").replace("_", " ").strip()


def _field_label(item: Optional[MetadataPlanItem], blob: str) -> str:
    text = f"{item.notes or ''} {blob}" if item else blob
    m = re.search(r"(?:field\s+)?label\s+(?:as\s+|is\s+|:)?\s*([A-Za-z][A-Za-z0-9 ]+)", text, re.I)
    if m:
        return m.group(1).strip().rstrip(".")
    if item:
        return _humanize_api(item.api_name)
    return "Custom Field"


def _field_data_type(item: MetadataPlanItem) -> str:
    notes = (item.notes or "").lower()
    if "picklist" in notes:
        return "Picklist"
    if "checkbox" in notes:
        return "Checkbox"
    if "number" in notes:
        return "Number"
    if "date" in notes:
        return "Date"
    if "text" in notes:
        return "Text"
    return "Custom Field"


def _mentions_picklist(item: Optional[MetadataPlanItem], blob: str) -> bool:
    text = f"{item.notes or ''} {blob}" if item else blob
    return "picklist" in text.lower()


def _picklist_values(item: Optional[MetadataPlanItem], blob: str) -> List[str]:
    text = f"{item.notes or ''} {blob}" if item else blob
    m = re.search(
        r"(?:picklist\s+)?values?\s*(?:are\s+|:)?\s*([A-Za-z0-9][A-Za-z0-9 ,/\-]{2,})",
        text,
        re.I,
    )
    if not m:
        return []
    raw = re.split(r"[.;]", m.group(1), maxsplit=1)[0]
    values = [v.strip() for v in raw.split(",") if v.strip()]
    skip = {"yes", "no", "none", "the following picklist values"}
    return [v for v in values if v.lower() not in skip]


def _default_value(
    item: Optional[MetadataPlanItem], blob: str, values: List[str]
) -> Optional[str]:
    text = f"{item.notes or ''} {blob}" if item else blob
    m = re.search(
        r"(?:set\s+)?([A-Za-z][A-Za-z0-9 \-]+?)\s+as\s+the\s+default",
        text,
        re.I,
    )
    if not m:
        m = re.search(
            r"default(?:\s+value)?(?:\s+is|\s+as|:)?\s+([A-Za-z][A-Za-z0-9 \-]+)",
            text,
            re.I,
        )
    if not m:
        return values[0] if len(values) == 1 else None
    candidate = m.group(1).strip().rstrip(".")
    if values:
        for value in values:
            if value.lower() == candidate.lower() or value.lower() in candidate.lower():
                return value
    return candidate


def _required_type_value(blob: str) -> Optional[str]:
    m = re.search(r"Type\s+is\s+([A-Za-z][A-Za-z0-9\-]*)", blob, re.I)
    if m:
        return m.group(1).strip().rstrip(".")
    if re.search(r"\bCustomer\b", blob):
        return "Customer"
    return None


def _first_custom_field_api(blob: str) -> Optional[str]:
    m = re.search(r"([A-Za-z][A-Za-z0-9_]*__c)", blob)
    return m.group(1) if m else None


def _validation_formula(
    rule: MetadataPlanItem,
    field_api: Optional[str],
    type_value: Optional[str],
) -> str:
    notes = rule.notes or ""
    field = field_api
    m_field = re.search(r"([A-Za-z][A-Za-z0-9_]*__c)", notes)
    if m_field:
        field = m_field.group(1)
    local_type = type_value
    m_type = re.search(r"Type\s+is\s+([A-Za-z][A-Za-z0-9\-]*)", notes, re.I)
    if m_type:
        local_type = m_type.group(1).strip()
    if local_type and field:
        return (
            "AND(\n"
            f'    ISPICKVAL(Type, "{local_type}"),\n'
            f"    ISBLANK({field})\n"
            ")"
        )
    if field:
        return f"ISBLANK({field})"
    return "/* formula from acceptance criteria */"


def _validation_error_message(
    rule: MetadataPlanItem, label: str, type_value: Optional[str]
) -> str:
    if type_value:
        return f"{label} is required for {type_value} {(rule.object or 'record').lower()}s."
    return f"{label} is required."


def _evaluation_rows(
    type_value: Optional[str], values: List[str], _label: str
) -> List[List[str]]:
    if not type_value:
        return []
    rows = [[type_value, "Blank", "Block save"]]
    for value in values:
        rows.append([type_value, value, "Allow save"])
    rows.extend(
        [
            ["Any other Type", "Blank", "Allow save"],
            ["Any other Type", "Populated", "Allow save"],
        ]
    )
    return rows


def _plan_scope_line(item: MetadataPlanItem) -> str:
    obj = f" on {item.object}" if item.object else ""
    return f"{item.action.title()} {item.metadata_type} {item.api_name}{obj}."


def _join_or(values: Sequence[str]) -> str:
    vals = [v for v in values if v]
    if not vals:
        return ""
    if len(vals) == 1:
        return vals[0]
    if len(vals) == 2:
        return f"{vals[0]} or {vals[1]}"
    return f"{', '.join(vals[:-1])}, or {vals[-1]}"


def _table_html(table: TechDocTable) -> str:
    lines = ["<table>", "<tbody>", "<tr>"]
    for header in table.headers:
        lines.append(f"<th><p>{_esc(header)}</p></th>")
    lines.append("</tr>")
    for row in table.rows:
        lines.append("<tr>")
        for cell in row:
            lines.append(f"<td><p>{_esc(cell)}</p></td>")
        lines.append("</tr>")
    lines.extend(["</tbody>", "</table>"])
    return "\n".join(lines)


def _esc(value: object) -> str:
    return html.escape(str(value or ""), quote=True)
