from __future__ import annotations

import json
from dataclasses import dataclass
from pathlib import Path
from typing import Any


@dataclass(frozen=True)
class PackageManifestInfo:
    root: Path
    package_root: Path
    manifest_path: Path | None = None
    version: str = ""
    diagnostics: tuple[dict[str, object], ...] = ()


def resolve_package_manifest(root: str | Path, *, installed: bool = True) -> PackageManifestInfo:
    package_root = Path(root).expanduser().resolve()
    if not installed or not package_root.is_dir():
        return PackageManifestInfo(root=package_root, package_root=package_root)

    manifest_path = _manifest_path(package_root)
    if manifest_path is None:
        return PackageManifestInfo(root=package_root, package_root=package_root)

    try:
        payload = json.loads(manifest_path.read_text(encoding="utf-8"))
    except json.JSONDecodeError as exc:
        return PackageManifestInfo(
            root=package_root,
            package_root=package_root,
            manifest_path=manifest_path,
            diagnostics=(
                {
                    "code": "invalid_package_manifest",
                    "message": f"Invalid package manifest JSON: {exc.msg}",
                    "path": str(manifest_path),
                },
            ),
        )
    except Exception as exc:  # noqa: BLE001
        return PackageManifestInfo(
            root=package_root,
            package_root=package_root,
            manifest_path=manifest_path,
            diagnostics=(
                {
                    "code": "unreadable_package_manifest",
                    "message": f"Package manifest could not be read: {exc}",
                    "path": str(manifest_path),
                },
            ),
        )
    if not isinstance(payload, dict):
        return PackageManifestInfo(
            root=package_root,
            package_root=package_root,
            manifest_path=manifest_path,
            diagnostics=(
                {
                    "code": "invalid_package_manifest",
                    "message": "Package manifest must be a JSON object.",
                    "path": str(manifest_path),
                },
            ),
        )

    version = _string_value(payload.get("version")) or ""
    resolved_root, diagnostics = _package_root_from_manifest(package_root, manifest_path, payload)
    return PackageManifestInfo(
        root=package_root,
        package_root=resolved_root,
        manifest_path=manifest_path,
        version=version,
        diagnostics=diagnostics,
    )


def _manifest_path(root: Path) -> Path | None:
    for filename in ("loushang-package.json", "plugin.json"):
        path = root / filename
        if path.is_file():
            return path
    return None


def _package_root_from_manifest(
    root: Path,
    manifest_path: Path,
    payload: dict[str, Any],
) -> tuple[Path, tuple[dict[str, object], ...]]:
    value = payload.get("packageRoot", payload.get("package_root", "."))
    if value in (None, ""):
        return root, ()
    if not isinstance(value, str):
        return root, (_invalid_package_root_diagnostic(manifest_path, "Package packageRoot must be a string."),)
    relative = Path(value).expanduser()
    if relative.is_absolute():
        return root, (_invalid_package_root_diagnostic(manifest_path, "Package packageRoot must be relative."),)
    resolved = (root / relative).resolve()
    try:
        resolved.relative_to(root)
    except ValueError:
        return root, (
            _invalid_package_root_diagnostic(manifest_path, "Package packageRoot must stay inside the package root."),
        )
    return resolved, ()


def _invalid_package_root_diagnostic(manifest_path: Path, message: str) -> dict[str, object]:
    return {
        "code": "invalid_package_manifest",
        "message": message,
        "path": str(manifest_path),
    }


def _string_value(value: object) -> str | None:
    if not isinstance(value, str):
        return None
    normalized = value.strip()
    return normalized or None
