"""
Salesforce metadata component inventory helpers.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional


class MetadataType(str, Enum):
    CUSTOM_FIELD = "CustomField"
    PERMISSION_SET = "PermissionSet"
    VALIDATION_RULE = "ValidationRule"
    QUEUE = "Queue"
    FLOW = "Flow"
    FLEXI_PAGE = "FlexiPage"
    LAYOUT = "Layout"
    APEX_CLASS = "ApexClass"
    APEX_TRIGGER = "ApexTrigger"
    LWC_BUNDLE = "LightningComponentBundle"
    RECORD_TYPE = "RecordType"


@dataclass(frozen=True)
class MetadataComponent:
    type: MetadataType
    api_name: str
    label: str | None = None
    source_path: Path | None = None
    properties: Dict[str, Any] = field(default_factory=dict)

    @property
    def parent_object(self) -> str | None:
        if self.type in {
            MetadataType.CUSTOM_FIELD,
            MetadataType.VALIDATION_RULE,
            MetadataType.LAYOUT,
            MetadataType.RECORD_TYPE,
        }:
            if "." in self.api_name:
                return self.api_name.split(".", 1)[0]
        return None

    @property
    def member_name(self) -> str:
        return self.api_name


@dataclass
class ComponentInventory:
    components: List[MetadataComponent]

    def by_type(self) -> Dict[str, List[MetadataComponent]]:
        grouped: Dict[str, List[MetadataComponent]] = {}
        for comp in self.components:
            grouped.setdefault(comp.type.value, []).append(comp)
        return grouped

    def package_xml_types(self) -> List[Dict[str, Any]]:
        grouped = self.by_type()
        payload: List[Dict[str, Any]] = []
        for type_name in sorted(grouped.keys()):
            members = sorted({comp.member_name for comp in grouped[type_name]})
            payload.append({"type": type_name, "members": members})
        return payload

    def as_map(self) -> Dict[tuple[str, str], MetadataComponent]:
        return {(comp.type.value, comp.api_name): comp for comp in self.components}


def scan_source_dir(force_app_path: Path, *, include: Optional[Iterable[MetadataType]] = None) -> ComponentInventory:
    root = _resolve_source_root(force_app_path)
    if not root.exists():
        raise ValueError(f"source directory not found: {root}")

    include_set = set(include) if include is not None else set(MetadataType)
    components: List[MetadataComponent] = []

    for path in sorted(root.rglob("*.xml")):
        if not path.is_file():
            continue
        rel = path.relative_to(root).as_posix()
        parts = rel.split("/")

        if _is_custom_field_path(parts, path.name):
            if MetadataType.CUSTOM_FIELD in include_set:
                obj = parts[1]
                field_name = _strip_suffix(path.name, ".field-meta.xml")
                api_name = f"{obj}.{field_name}"
                components.append(
                    MetadataComponent(
                        type=MetadataType.CUSTOM_FIELD,
                        api_name=api_name,
                        source_path=path,
                    )
                )
            continue

        if _is_validation_rule_path(parts, path.name):
            if MetadataType.VALIDATION_RULE in include_set:
                obj = parts[1]
                rule_name = _strip_suffix(path.name, ".validationRule-meta.xml")
                api_name = f"{obj}.{rule_name}"
                components.append(
                    MetadataComponent(
                        type=MetadataType.VALIDATION_RULE,
                        api_name=api_name,
                        source_path=path,
                    )
                )
            continue

        if _is_permission_set_path(parts, path.name):
            if MetadataType.PERMISSION_SET in include_set:
                perm_name = _strip_suffix(path.name, ".permissionset-meta.xml")
                components.append(
                    MetadataComponent(
                        type=MetadataType.PERMISSION_SET,
                        api_name=perm_name,
                        source_path=path,
                    )
                )
            continue

        if _is_queue_path(parts, path.name):
            if MetadataType.QUEUE in include_set:
                queue_name = _strip_suffix(path.name, ".queue-meta.xml")
                components.append(
                    MetadataComponent(
                        type=MetadataType.QUEUE,
                        api_name=queue_name,
                        source_path=path,
                    )
                )
            continue

        if _is_flow_path(parts, path.name):
            if MetadataType.FLOW in include_set:
                flow_name = _strip_suffix(path.name, ".flow-meta.xml")
                components.append(
                    MetadataComponent(
                        type=MetadataType.FLOW,
                        api_name=flow_name,
                        source_path=path,
                    )
                )
            continue

        if _is_flexi_page_path(parts, path.name):
            if MetadataType.FLEXI_PAGE in include_set:
                flexi_page_name = _strip_suffix(path.name, ".flexipage-meta.xml")
                components.append(
                    MetadataComponent(
                        type=MetadataType.FLEXI_PAGE,
                        api_name=flexi_page_name,
                        source_path=path,
                    )
                )
            continue

        if _is_layout_path(parts, path.name):
            if MetadataType.LAYOUT in include_set:
                obj = parts[1]
                layout_name = _strip_suffix(path.name, ".layout-meta.xml")
                components.append(
                    MetadataComponent(
                        type=MetadataType.LAYOUT,
                        api_name=f"{obj}.{layout_name}",
                        source_path=path,
                    )
                )
            continue

        if _is_record_type_path(parts, path.name):
            if MetadataType.RECORD_TYPE in include_set:
                obj = parts[1]
                record_type_name = _strip_suffix(path.name, ".recordType-meta.xml")
                components.append(
                    MetadataComponent(
                        type=MetadataType.RECORD_TYPE,
                        api_name=f"{obj}.{record_type_name}",
                        source_path=path,
                    )
                )
            continue

        if _is_apex_class_path(parts, path.name):
            if MetadataType.APEX_CLASS in include_set:
                class_name = _strip_suffix(path.name, ".cls-meta.xml")
                components.append(
                    MetadataComponent(
                        type=MetadataType.APEX_CLASS,
                        api_name=class_name,
                        source_path=path,
                    )
                )
            continue

        if _is_apex_trigger_path(parts, path.name):
            if MetadataType.APEX_TRIGGER in include_set:
                trigger_name = _strip_suffix(path.name, ".trigger-meta.xml")
                components.append(
                    MetadataComponent(
                        type=MetadataType.APEX_TRIGGER,
                        api_name=trigger_name,
                        source_path=path,
                    )
                )
            continue

    # LWC bundles are directory-based, not single XML files.
    if MetadataType.LWC_BUNDLE in include_set:
        lwc_dir = root / "lwc"
        if lwc_dir.exists() and lwc_dir.is_dir():
            for child in sorted(lwc_dir.iterdir()):
                if child.is_dir() and not child.name.startswith("__"):
                    components.append(
                        MetadataComponent(
                            type=MetadataType.LWC_BUNDLE,
                            api_name=child.name,
                            source_path=child,
                        )
                    )

    return ComponentInventory(components=components)


def _resolve_source_root(force_app_path: Path) -> Path:
    root = force_app_path
    candidate = root / "main" / "default"
    if candidate.exists():
        return candidate
    return root


def _strip_suffix(value: str, suffix: str) -> str:
    if value.endswith(suffix):
        return value[: -len(suffix)]
    return value


def _is_custom_field_path(parts: List[str], name: str) -> bool:
    if len(parts) < 4:
        return False
    return parts[0] == "objects" and parts[2] == "fields" and name.endswith(".field-meta.xml")


def _is_validation_rule_path(parts: List[str], name: str) -> bool:
    if len(parts) < 4:
        return False
    return parts[0] == "objects" and parts[2] == "validationRules" and name.endswith(
        ".validationRule-meta.xml"
    )


def _is_permission_set_path(parts: List[str], name: str) -> bool:
    if len(parts) < 2:
        return False
    return parts[0] == "permissionsets" and name.endswith(".permissionset-meta.xml")


def _is_queue_path(parts: List[str], name: str) -> bool:
    if len(parts) < 2:
        return False
    return parts[0] == "queues" and name.endswith(".queue-meta.xml")


def _is_flow_path(parts: List[str], name: str) -> bool:
    return len(parts) >= 2 and parts[0] == "flows" and name.endswith(".flow-meta.xml")


def _is_flexi_page_path(parts: List[str], name: str) -> bool:
    return len(parts) >= 2 and parts[0] == "flexipages" and name.endswith(".flexipage-meta.xml")


def _is_layout_path(parts: List[str], name: str) -> bool:
    if len(parts) < 4:
        return False
    return parts[0] == "objects" and parts[2] == "layouts" and name.endswith(".layout-meta.xml")


def _is_record_type_path(parts: List[str], name: str) -> bool:
    if len(parts) < 4:
        return False
    return parts[0] == "objects" and parts[2] == "recordTypes" and name.endswith(".recordType-meta.xml")


def _is_apex_class_path(parts: List[str], name: str) -> bool:
    if len(parts) < 2:
        return False
    return parts[0] == "classes" and name.endswith(".cls-meta.xml")


def _is_apex_trigger_path(parts: List[str], name: str) -> bool:
    if len(parts) < 2:
        return False
    return parts[0] == "triggers" and name.endswith(".trigger-meta.xml")
