from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, List


@dataclass
class EvidencePointer:
    # minimal placeholder for evidence pointing
    path: str
    excerpt: str | None = None


@dataclass
class Constraint:
    # minimal placeholder for constraints
    key: str
    value: Any = None


@dataclass
class Manifest:
    files: List[Dict[str, Any]]


def _load_yaml_native(path: str) -> dict:
    # Use PyYAML when available. If the loaded document is not a mapping,
    # return an empty mapping to keep the API stable for callers.
    import yaml  # type: ignore

    with open(path, "r", encoding="utf-8") as f:
        loaded = yaml.safe_load(f)

    if not isinstance(loaded, dict):
        return {}

    return loaded


def _load_yaml_fallback(path: str) -> dict:
    # very small fallback that recognises a top-level files: list and
    # simple key: value lines. It intentionally is tiny and forgiving.
    result: dict = {}
    files: List[Dict[str, Any]] = []
    current: Dict[str, Any] | None = None

    with open(path, "r", encoding="utf-8") as f:
        for raw in f:
            line = raw.rstrip("\n")
            stripped = line.lstrip()
            if not stripped or stripped.startswith("#"):
                continue
            if stripped.startswith("files:") and line.startswith(stripped):
                continue
            if stripped.startswith("- "):
                if current is not None:
                    files.append(current)
                current = {}
                rest = stripped[2:].strip()
                if rest and ":" in rest:
                    k, v = rest.split(":", 1)
                    current[k.strip()] = v.strip().strip("'\"")
                continue
            if ":" in stripped and current is not None:
                k, v = stripped.split(":", 1)
                current[k.strip()] = v.strip().strip("'\"")

    if current is not None:
        files.append(current)
    if files:
        result["files"] = files
    return result


def parse_manifest(manifest_path: str) -> Manifest:
    """Parse a minimal manifest file and return a Manifest dataclass.

    The function will attempt to use PyYAML (yaml.safe_load) when available;
    otherwise it falls back to a tiny parser that recognises a top-level
    `files:` list and simple mappings. The returned Manifest normalises
    `file_path` -> `path` when present.
    """
    try:
        manifest = _load_yaml_native(manifest_path)
    except Exception:
        manifest = _load_yaml_fallback(manifest_path)

    # Be defensive: ensure we always operate on a mapping
    if not isinstance(manifest, dict):
        manifest = {}

    files = manifest.get("files") or []
    normalized: List[Dict[str, Any]] = []
    for entry in files:
        if not isinstance(entry, dict):
            # coerce simple scalar entries
            entry = {"path": str(entry)}
        # prefer path over file_path
        if "file_path" in entry and "path" not in entry:
            entry = dict(entry)
            entry["path"] = entry.pop("file_path")
        normalized.append(entry)

    return Manifest(files=normalized)


__all__ = ["Manifest", "Constraint", "EvidencePointer", "parse_manifest"]
