"""Generate lightweight markdown stubs for registry entities."""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from pathlib import Path

import yaml

from .tracking.entities import EntityEntry, EntityRegistry, VALID_ENTITY_TYPES, normalize_entity_name

logger = logging.getLogger(__name__)

AUTO_GENERATED_STUB_MARKER = "*Entity stub - auto-generated by `folio entities generate-stubs`*"
_INVALID_FILENAME_CHARS = '<>:"/\\|?*'


@dataclass
class StubGenerationResult:
    generated: int = 0
    skipped_existing: int = 0
    skipped_manual: int = 0
    generated_by_type: dict[str, int] = field(
        default_factory=lambda: {entity_type: 0 for entity_type in VALID_ENTITY_TYPES}
    )
    warnings: list[str] = field(default_factory=list)


def generate_entity_stubs(
    *,
    registry: EntityRegistry,
    output_dir: Path,
    force: bool = False,
) -> StubGenerationResult:
    """Create entity stubs under the requested output directory."""
    result = StubGenerationResult()
    output_dir.mkdir(parents=True, exist_ok=True)

    for entity_type, entity_key, entry in registry.iter_entities():
        stub_path = output_dir / entity_type / f"{_stub_filename(entry.canonical_name)}.md"
        stub_path.parent.mkdir(parents=True, exist_ok=True)

        if stub_path.exists():
            if not force:
                result.skipped_existing += 1
                continue
            try:
                existing_content = stub_path.read_text(encoding="utf-8")
            except OSError as exc:
                result.skipped_manual += 1
                warning = f"Could not read existing stub {stub_path.name}: {exc}"
                logger.warning(warning)
                result.warnings.append(warning)
                continue
            if AUTO_GENERATED_STUB_MARKER not in existing_content:
                result.skipped_manual += 1
                warning = (
                    f"Skipping manual stub {stub_path.name}: file does not contain the auto-generated marker."
                )
                logger.warning(warning)
                result.warnings.append(warning)
                continue

        stub_path.write_text(
            _render_stub(registry=registry, entity_type=entity_type, entity_key=entity_key, entry=entry),
            encoding="utf-8",
        )
        result.generated += 1
        result.generated_by_type[entity_type] += 1

    return result


def _render_stub(
    *,
    registry: EntityRegistry,
    entity_type: str,
    entity_key: str,
    entry: EntityEntry,
) -> str:
    frontmatter: dict[str, object] = {
        "id": f"entity/{entity_type}/{entity_key.replace('_', '-')}",
        "title": entry.canonical_name,
        "type": "entity",
        "entity_type": entity_type,
        "status": "unconfirmed" if entry.needs_confirmation else "confirmed",
        "aliases": list(entry.aliases or []),
        "tags": [],
    }

    body_lines = [f"# {entry.canonical_name}", "", AUTO_GENERATED_STUB_MARKER]

    if entity_type == "person":
        reports_to = _reports_to_display_name(registry, entry.reports_to)
        if entry.org_level:
            frontmatter["org_level"] = entry.org_level
        if reports_to:
            frontmatter["reports_to"] = reports_to
        if entry.title:
            frontmatter["job_title"] = entry.title

        summary_parts: list[str] = []
        if entry.org_level:
            summary_parts.append(f"**Level:** {entry.org_level}")
        if reports_to:
            summary_parts.append(f"**Reports to:** [[{reports_to}]]")
        if entry.title:
            summary_parts.append(f"**Title:** {entry.title}")
        if summary_parts:
            body_lines.extend(["", " | ".join(summary_parts)])

    yaml_str = yaml.dump(
        frontmatter,
        default_flow_style=False,
        sort_keys=False,
        allow_unicode=True,
    )
    return f"---\n{yaml_str}---\n\n" + "\n".join(body_lines) + "\n"


def _reports_to_display_name(registry: EntityRegistry, reports_to: str | None) -> str | None:
    """Return a display-safe manager name for stub rendering.

    Missing registry references fall back to the stored raw value so legacy
    data still renders visibly instead of dropping the relationship.
    """
    if not reports_to:
        return None
    resolved = registry.resolve_key_to_name(reports_to, "person")
    return normalize_entity_name(resolved or reports_to) or None


def _stub_filename(name: str) -> str:
    cleaned = normalize_entity_name(name)
    sanitized = []
    for char in cleaned:
        if char in _INVALID_FILENAME_CHARS or ord(char) < 32:
            sanitized.append("-")
        else:
            sanitized.append(char)
    filename = "".join(sanitized).rstrip(" .")
    return filename or "entity"
