from __future__ import annotations

import os
from pathlib import Path
import shutil
from typing import Any

from liverylab_core import (
    official_external_liveries,
    official_external_project_assets,
    official_saved_car_generation,
    official_saved_car_state,
    official_saved_car_templates,
    project_capabilities,
    saved_games_paths,
)
from liverylab_core.proto import ProtoField, build_fields


RUNTIME_SUFFIXES = (".texture", ".texturemips")
HEALTH_SEVERITY_ORDER = {"error": 0, "warning": 1, "info": 2, "ok": 3}
PROJECT_THUMBNAIL_FILENAME = "livery_thumbnail.png"
SAVED_CAR_THUMBNAIL_SIZE = (1024, 1024)


def _safe_external_folder_name(value: Any, fallback: str) -> str:
    raw = str(value or "").strip()
    if raw:
        return official_external_liveries._safe_segment(raw, fallback)
    return official_external_liveries._safe_segment(fallback, "official_livery")


def _external_folder_name(entry: dict[str, Any], project_folder: Path) -> str:
    details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
    return _safe_external_folder_name(
        details.get("externalFolderName")
        or details.get("plannedExternalFolder")
        or details.get("sourceFolderName")
        or entry.get("skin_id")
        or project_folder.name,
        project_folder.name,
    )


def _saved_car_matches(car_guid: str, saved_games_ace_root: str | Path | None = None) -> list[dict[str, Any]]:
    guid = str(car_guid or "").strip().lower()
    rows: list[dict[str, Any]] = []
    if not guid:
        return rows
    for root in project_capabilities.saved_cars_roots(saved_games_ace_root):
        for path in sorted(root.glob(f"*{official_external_liveries.SAVED_CAR_SUFFIX}"), key=lambda item: str(item).lower()):
            stem = path.stem.lower()
            if stem.endswith(f"_{guid}") or guid in stem:
                rows.append(
                    {
                        "path": str(path),
                        "name": path.name,
                        "root": str(root),
                        "size": path.stat().st_size if path.exists() else 0,
                    }
                )
    return rows


def _saved_car_deployment_plan(
    entry: dict[str, Any],
    project_folder: Path,
    car_guid: str,
    ace_root: Path,
    existing_rows: list[dict[str, Any]],
    preview_assets_root: str | Path | None = None,
) -> dict[str, Any]:
    prepared = official_saved_car_generation.project_saved_car_status(
        entry,
        project_folder,
        preview_assets_root,
    )
    roots = project_capabilities.saved_cars_deployment_roots(ace_root)
    errors: list[str] = []
    warnings: list[str] = []
    valid_existing: list[dict[str, Any]] = []
    invalid_existing: list[dict[str, Any]] = []
    for row in existing_rows:
        path = Path(str(row.get("path") or "")).expanduser()
        inspection = official_saved_car_state.inspect_saved_car_file(path)
        checked = {**row, "inspection": inspection}
        if (
            inspection.get("valid")
            and inspection.get("wrapped")
            and official_saved_car_state.matches_official_livery_guid(inspection, car_guid)
        ):
            valid_existing.append(checked)
        else:
            invalid_existing.append(checked)

    managed = bool(prepared.get("managed"))
    action = "none"
    target = ""
    target_root = ""
    if valid_existing:
        action = "existing"
        target = str(valid_existing[0].get("path") or "")
        target_root = str(valid_existing[0].get("root") or "")
    elif managed:
        if not prepared.get("valid"):
            errors.extend(str(row) for row in (prepared.get("errors") or []) if str(row).strip())
            if not errors:
                errors.append("Prepared project SavedCar is not valid.")
        elif invalid_existing:
            errors.append("A matching SavedCar filename exists but its payload is invalid or uses a different inner GUID.")
        elif not roots:
            errors.append(
                "Automatic SavedCar deployment could not find a profile target. Start AC EVO once, "
                "create or save a car, close the game, and try again."
            )
        elif len(roots) > 1:
            detected_roots = "\n".join(f"- {root}" for root in roots)
            errors.append(
                "Automatic SavedCar deployment found multiple profile SavedCars targets; "
                f"found {len(roots)}. The active profile cannot be selected safely.\n"
                f"Detected profile folders:\n{detected_roots}"
            )
        else:
            action = "copy"
            target_root = str(roots[0])
            target = str(roots[0] / Path(str(prepared.get("path") or "")).name)
    elif car_guid and not valid_existing:
        warnings.append("No matching SavedCar file found for car_guid.")

    return {
        "managed": managed,
        "sourceType": prepared.get("sourceType") or "",
        "action": action,
        "ready": not errors and (action in {"copy", "existing"} or not managed),
        "source": prepared.get("path") or "",
        "sourceRelativePath": prepared.get("relativePath") or "",
        "sourceExists": bool(prepared.get("exists")),
        "sourceValid": bool(prepared.get("valid")),
        "target": target,
        "targetRoot": target_root,
        "targetRootExists": bool(target_root and Path(target_root).is_dir()),
        "targetRootWillBeCreated": bool(action == "copy" and target_root and not Path(target_root).exists()),
        "profileSavedCarsRoots": [str(root) for root in roots],
        "profileSavedCarsRootCount": len(roots),
        "carGuid": car_guid,
        "carModel": prepared.get("carModel") or entry.get("car_model") or "",
        "existing": valid_existing,
        "existingCount": len(valid_existing),
        "invalidExisting": invalid_existing,
        "invalidExistingCount": len(invalid_existing),
        "prepared": prepared,
        "warnings": warnings,
        "errors": errors,
    }


def _apply_saved_car_plan(plan: dict[str, Any]) -> dict[str, Any]:
    action = str(plan.get("action") or "none")
    if action == "existing":
        return {
            "status": "existing",
            "created": False,
            "source": plan.get("source") or "",
            "target": plan.get("target") or "",
            "carGuid": plan.get("carGuid") or "",
        }
    if action == "none":
        return {
            "status": "not_managed",
            "created": False,
            "source": plan.get("source") or "",
            "target": plan.get("target") or "",
            "carGuid": plan.get("carGuid") or "",
        }
    if action != "copy" or not plan.get("ready"):
        raise RuntimeError("Automatic SavedCar deployment plan is not ready.")

    source = Path(str(plan.get("source") or "")).expanduser()
    target = Path(str(plan.get("target") or "")).expanduser()
    target_root = Path(str(plan.get("targetRoot") or "")).expanduser()
    if not source.exists() or not source.is_file():
        raise FileNotFoundError(f"Prepared project SavedCar is missing: {source}")
    try:
        target.resolve().relative_to(target_root.resolve())
    except Exception as exc:
        raise RuntimeError(f"Refusing to deploy SavedCar outside the selected profile root: {target}") from exc
    if target.exists():
        inspection = official_saved_car_state.inspect_saved_car_file(target)
        if inspection.get("valid") and official_saved_car_state.matches_official_livery_guid(inspection, plan.get("carGuid")):
            return {
                "status": "existing",
                "created": False,
                "source": str(source),
                "target": str(target),
                "carGuid": plan.get("carGuid") or "",
            }
        raise FileExistsError(f"SavedCar target already exists with incompatible content: {target}")

    target.parent.mkdir(parents=True, exist_ok=True)
    temp = target.with_name(target.name + ".liverylab-tmp")
    try:
        shutil.copy2(source, temp)
        os.replace(temp, target)
    finally:
        if temp.exists():
            temp.unlink()
    inspection = official_saved_car_state.inspect_saved_car_file(target)
    if not inspection.get("valid") or not official_saved_car_state.matches_official_livery_guid(inspection, plan.get("carGuid")):
        target.unlink(missing_ok=True)
        raise RuntimeError(f"Deployed SavedCar failed validation: {target}")
    return {
        "status": "installed",
        "created": True,
        "source": str(source),
        "target": str(target),
        "carGuid": plan.get("carGuid") or "",
        "size": target.stat().st_size,
    }


def _saved_car_thumbnail_plan(project_folder: Path, car_guid: str, ace_root: Path) -> dict[str, Any]:
    source = project_folder / PROJECT_THUMBNAIL_FILENAME
    guid = official_saved_car_state.normalize_guid(car_guid)
    thumbnail_root = ace_root / "SavedCars" / "Thumbnails"
    target_texture = thumbnail_root / f"{guid}.texture" if guid else thumbnail_root
    target_mips = target_texture.with_suffix(".texturemips") if guid else thumbnail_root
    source_ready = source.is_file()
    source_error = ""
    source_size: tuple[int, int] | None = None
    if source_ready:
        try:
            from PIL import Image

            with Image.open(source) as image:
                image.verify()
            with Image.open(source) as image:
                source_size = (int(image.width), int(image.height))
        except Exception as exc:
            source_ready = False
            source_error = str(exc)

    texture_exists = bool(guid and target_texture.is_file())
    mips_exists = bool(guid and target_mips.is_file())
    if not guid:
        action = "unavailable"
        reason = "SavedCar GUID is missing"
    elif not source.is_file():
        action = "unavailable"
        reason = "Project thumbnail is missing"
    elif not source_ready:
        action = "invalid"
        reason = f"Project thumbnail is invalid: {source_error}"
    elif texture_exists and mips_exists:
        action = "existing"
        reason = "AC EVO SavedCar thumbnail already exists"
    elif texture_exists or mips_exists:
        action = "partial_existing"
        reason = "A partial AC EVO SavedCar thumbnail already exists and will not be overwritten"
    else:
        action = "create"
        reason = "Project thumbnail is ready for the AC EVO SavedCar"
    return {
        "action": action,
        "ready": action in {"create", "existing", "unavailable"},
        "reason": reason,
        "carGuid": guid,
        "source": str(source),
        "sourceExists": source.is_file(),
        "sourceSize": {"width": source_size[0], "height": source_size[1]} if source_size else {},
        "targetRoot": str(thumbnail_root),
        "targetTexture": str(target_texture) if guid else "",
        "targetMips": str(target_mips) if guid else "",
        "targetTextureExists": texture_exists,
        "targetMipsExists": mips_exists,
        "willOverwrite": False,
    }


def _saved_car_thumbnail_descriptor() -> bytes:
    return build_fields([
        ProtoField(1, 0, SAVED_CAR_THUMBNAIL_SIZE[0]),
        ProtoField(2, 0, SAVED_CAR_THUMBNAIL_SIZE[1]),
        ProtoField(3, 0, 1),
        ProtoField(4, 0, 3),
        ProtoField(6, 0, 1),
    ])


def _apply_saved_car_thumbnail_plan(plan: dict[str, Any]) -> dict[str, Any]:
    action = str(plan.get("action") or "unavailable")
    if action != "create":
        return {
            "status": action,
            "created": False,
            "reason": plan.get("reason") or "",
            "source": plan.get("source") or "",
            "texture": plan.get("targetTexture") or "",
            "textureMips": plan.get("targetMips") or "",
        }
    source = Path(str(plan.get("source") or "")).expanduser()
    target_texture = Path(str(plan.get("targetTexture") or "")).expanduser()
    target_mips = Path(str(plan.get("targetMips") or "")).expanduser()
    target_root = Path(str(plan.get("targetRoot") or "")).expanduser()
    try:
        target_texture.resolve().relative_to(target_root.resolve())
        target_mips.resolve().relative_to(target_root.resolve())
    except Exception as exc:
        raise RuntimeError("Refusing to create a SavedCar thumbnail outside its thumbnail root") from exc
    if target_texture.exists() or target_mips.exists():
        return {
            "status": "existing" if target_texture.is_file() and target_mips.is_file() else "partial_existing",
            "created": False,
            "reason": "SavedCar thumbnail target appeared after preflight and was not overwritten",
            "source": str(source),
            "texture": str(target_texture),
            "textureMips": str(target_mips),
        }

    from PIL import Image, ImageOps

    with Image.open(source) as image:
        rgba = image.convert("RGBA")
        if rgba.size != SAVED_CAR_THUMBNAIL_SIZE:
            rgba = ImageOps.fit(rgba, SAVED_CAR_THUMBNAIL_SIZE, method=Image.Resampling.LANCZOS)
        raw = rgba.tobytes("raw", "RGBA")
    expected_size = SAVED_CAR_THUMBNAIL_SIZE[0] * SAVED_CAR_THUMBNAIL_SIZE[1] * 4
    if len(raw) != expected_size:
        raise RuntimeError(f"SavedCar thumbnail RGBA payload has {len(raw):,} bytes; expected {expected_size:,}")

    target_root.mkdir(parents=True, exist_ok=True)
    created_mips = False
    created_texture = False
    try:
        with target_mips.open("xb") as handle:
            handle.write(raw)
        created_mips = True
        with target_texture.open("xb") as handle:
            handle.write(_saved_car_thumbnail_descriptor())
        created_texture = True
    except Exception:
        if created_texture:
            target_texture.unlink(missing_ok=True)
        if created_mips:
            target_mips.unlink(missing_ok=True)
        raise
    return {
        "status": "created",
        "created": True,
        "reason": "Project thumbnail installed for the AC EVO SavedCar",
        "source": str(source),
        "texture": str(target_texture),
        "textureMips": str(target_mips),
        "width": SAVED_CAR_THUMBNAIL_SIZE[0],
        "height": SAVED_CAR_THUMBNAIL_SIZE[1],
        "bytes": len(raw),
    }


def _copy_plan(project_folder: Path, target_folder: Path, asset_status: dict[str, Any]) -> list[dict[str, Any]]:
    rows = [
        {
            "kind": "manifest",
            "name": official_external_liveries.EXTERNAL_LIVERY_JSON,
            "source": str(project_folder / official_external_liveries.EXTERNAL_LIVERY_JSON),
            "target": str(target_folder / official_external_liveries.EXTERNAL_LIVERY_JSON),
            "exists": (project_folder / official_external_liveries.EXTERNAL_LIVERY_JSON).is_file(),
        }
    ]
    for slot in asset_status.get("slots") or []:
        if not isinstance(slot, dict) or not slot.get("referenced"):
            continue
        filename = str(slot.get("relativePath") or slot.get("filename") or "").strip()
        if not filename:
            continue
        rows.append(
            {
                "kind": "texture_png",
                "slot": slot.get("id") or "",
                "label": slot.get("label") or filename,
                "name": filename,
                "source": str(project_folder / filename),
                "target": str(target_folder / filename),
                "exists": bool(slot.get("exists")),
            }
        )
    return rows


def _stale_runtime_rows(target_folder: Path, copy_rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    for row in copy_rows:
        if row.get("kind") != "texture_png":
            continue
        target = Path(str(row.get("target") or ""))
        for suffix in RUNTIME_SUFFIXES:
            runtime = target.with_suffix(suffix)
            if runtime.exists() and runtime.is_file():
                rows.append(
                    {
                        "sourcePng": target.name,
                        "kind": suffix.lstrip("."),
                        "path": str(runtime),
                        "name": runtime.name,
                    }
                )
    return rows


def _guid_conflict_rows(external_root: Path, target_folder: Path, car_guid: str) -> list[dict[str, Any]]:
    guid = str(car_guid or "").strip().lower()
    rows: list[dict[str, Any]] = []
    if not guid or not external_root.exists() or not external_root.is_dir():
        return rows
    try:
        target_resolved = target_folder.resolve()
    except Exception:
        target_resolved = target_folder
    for json_path in sorted(external_root.glob(f"*/{official_external_liveries.EXTERNAL_LIVERY_JSON}"), key=lambda item: str(item).lower()):
        folder = json_path.parent
        try:
            if folder.resolve() == target_resolved:
                continue
        except Exception:
            if str(folder).lower() == str(target_folder).lower():
                continue
        try:
            data, warnings = official_external_liveries._read_external_livery_json(json_path)
        except Exception as exc:
            rows.append({
                "folderName": folder.name,
                "folder": str(folder),
                "externalLiveryJson": str(json_path),
                "carGuid": "",
                "readable": False,
                "warnings": [str(exc)],
            })
            continue
        other_guid = str(data.get("car_guid") or "").strip().lower()
        if other_guid != guid:
            continue
        rows.append({
            "folderName": folder.name,
            "folder": str(folder),
            "externalLiveryJson": str(json_path),
            "carGuid": str(data.get("car_guid") or "").strip(),
            "readable": True,
            "warnings": warnings,
        })
    return rows


def _copy_file(source: Path, target: Path) -> dict[str, Any]:
    target.parent.mkdir(parents=True, exist_ok=True)
    before_exists = target.exists()
    before_size = target.stat().st_size if before_exists and target.is_file() else 0
    if source.name == official_external_liveries.EXTERNAL_LIVERY_JSON:
        data, _warnings = official_external_liveries._read_external_livery_json(source)
        official_external_liveries.write_external_livery_json(target, data)
    else:
        shutil.copy2(source, target)
    return {
        "source": str(source),
        "target": str(target),
        "name": target.name,
        "created": not before_exists,
        "overwritten": before_exists,
        "previousSize": before_size,
        "size": target.stat().st_size if target.exists() else 0,
    }


def apply_preflight(preflight: dict[str, Any]) -> dict[str, Any]:
    if not isinstance(preflight, dict):
        raise ValueError("Deployment preflight is required")
    if not preflight.get("ready"):
        errors = preflight.get("errors") if isinstance(preflight.get("errors"), list) else []
        detail = "; ".join(str(row) for row in errors[:4]) or str(preflight.get("status") or "not ready")
        raise RuntimeError(f"Official deployment preflight is not ready: {detail}")

    target_folder = Path(str(preflight.get("targetFolder") or "")).expanduser()
    external_root = Path(str(preflight.get("externalLiveriesRoot") or "")).expanduser()
    if not str(target_folder):
        raise ValueError("Deployment target folder is missing")
    try:
        target_folder.resolve().relative_to(external_root.resolve())
    except Exception as exc:
        raise RuntimeError(f"Refusing to deploy outside ExternalLiveries root: {target_folder}") from exc

    copy_rows = preflight.get("copyPlan") if isinstance(preflight.get("copyPlan"), list) else []
    saved_car_plan = preflight.get("savedCarDeployment") if isinstance(preflight.get("savedCarDeployment"), dict) else {}
    saved_car_deployment = _apply_saved_car_plan(saved_car_plan)
    thumbnail_plan = preflight.get("savedCarThumbnail") if isinstance(preflight.get("savedCarThumbnail"), dict) else {}
    try:
        saved_car_thumbnail = _apply_saved_car_thumbnail_plan(thumbnail_plan)
    except Exception as exc:
        saved_car_thumbnail = {
            "status": "error",
            "created": False,
            "reason": str(exc),
            "source": thumbnail_plan.get("source") or "",
            "texture": thumbnail_plan.get("targetTexture") or "",
            "textureMips": thumbnail_plan.get("targetMips") or "",
        }
    copied: list[dict[str, Any]] = []
    skipped: list[dict[str, Any]] = []
    deleted_runtime: list[dict[str, Any]] = []
    errors: list[str] = []

    for row in copy_rows:
        if not isinstance(row, dict):
            continue
        source = Path(str(row.get("source") or "")).expanduser()
        target = Path(str(row.get("target") or "")).expanduser()
        if not source.exists() or not source.is_file():
            errors.append(f"Missing source: {source}")
            continue
        try:
            copied_row = _copy_file(source, target)
            copied_row["kind"] = row.get("kind") or ""
            copied_row["slot"] = row.get("slot") or ""
            copied.append(copied_row)
            if row.get("kind") == "texture_png":
                for suffix in RUNTIME_SUFFIXES:
                    runtime = target.with_suffix(suffix)
                    if runtime.exists() and runtime.is_file():
                        size = runtime.stat().st_size
                        runtime.unlink()
                        deleted_runtime.append({
                            "sourcePng": target.name,
                            "kind": suffix.lstrip("."),
                            "path": str(runtime),
                            "name": runtime.name,
                            "size": size,
                        })
        except Exception as exc:
            errors.append(f"{target.name or target}: {exc}")

    verification = {
        "ready": False,
        "targetFolder": str(target_folder),
        "externalLiveryJson": str(target_folder / official_external_liveries.EXTERNAL_LIVERY_JSON),
        "savedCarPath": str(saved_car_deployment.get("target") or ""),
        "carGuid": str(saved_car_plan.get("carGuid") or ""),
        "errors": [],
    }
    if not target_folder.is_dir():
        verification["errors"].append(f"ExternalLiveries target folder is missing after deployment: {target_folder}")
    external_json = target_folder / official_external_liveries.EXTERNAL_LIVERY_JSON
    if not external_json.is_file():
        verification["errors"].append(f"external_livery.json is missing after deployment: {external_json}")
    for copied_row in copied:
        copied_target = Path(str(copied_row.get("target") or "")).expanduser()
        if not copied_target.is_file():
            verification["errors"].append(f"Deployed file is missing after copy: {copied_target}")
    if saved_car_plan.get("managed"):
        saved_target = Path(str(saved_car_deployment.get("target") or "")).expanduser()
        if not saved_target.is_file():
            verification["errors"].append(f"Garage SavedCar is missing after deployment: {saved_target}")
        else:
            saved_inspection = official_saved_car_state.inspect_saved_car_file(saved_target)
            expected_guid = official_saved_car_state.normalize_guid(saved_car_plan.get("carGuid"))
            if not saved_inspection.get("valid") or not saved_inspection.get("wrapped"):
                verification["errors"].append(f"Garage SavedCar is invalid after deployment: {saved_target}")
            elif expected_guid and not official_saved_car_state.matches_official_livery_guid(saved_inspection, expected_guid):
                verification["errors"].append(f"Garage SavedCar livery identity does not match after deployment: {saved_target}")
    verification["ready"] = not verification["errors"]
    errors.extend(str(row) for row in verification["errors"])

    if errors:
        if saved_car_thumbnail.get("created"):
            Path(str(saved_car_thumbnail.get("texture") or "")).unlink(missing_ok=True)
            Path(str(saved_car_thumbnail.get("textureMips") or "")).unlink(missing_ok=True)
        if saved_car_deployment.get("created"):
            saved_target = Path(str(saved_car_deployment.get("target") or "")).expanduser()
            if saved_target.exists() and saved_target.is_file():
                saved_target.unlink()
        for copied_row in reversed(copied):
            if copied_row.get("created"):
                Path(str(copied_row.get("target") or "")).unlink(missing_ok=True)
        raise RuntimeError("Official deployment failed: " + "; ".join(errors[:6]))

    external_copied_count = len(copied)
    saved_car_copied_count = 1 if saved_car_deployment.get("created") else 0
    return {
        "status": "deployed",
        "targetFolder": str(target_folder),
        "copied": copied,
        "copiedCount": external_copied_count + saved_car_copied_count,
        "externalCopiedCount": external_copied_count,
        "savedCarCopiedCount": saved_car_copied_count,
        "savedCar": saved_car_deployment,
        "savedCarThumbnail": saved_car_thumbnail,
        "verification": verification,
        "skipped": skipped,
        "skippedCount": len(skipped),
        "deletedRuntimeFiles": deleted_runtime,
        "deletedRuntimeFileCount": len(deleted_runtime),
    }


def _health_row(
    key: str,
    severity: str,
    title: str,
    message: str,
    fix: str = "",
    details: dict[str, Any] | None = None,
) -> dict[str, Any]:
    return {
        "key": key,
        "severity": severity if severity in HEALTH_SEVERITY_ORDER else "info",
        "title": title,
        "message": message,
        "fix": fix,
        "details": details or {},
    }


def _safe_int(value: Any) -> int:
    try:
        return int(value or 0)
    except (TypeError, ValueError):
        return 0


def health_from_preflight(preflight: dict[str, Any]) -> dict[str, Any]:
    asset_status = preflight.get("assetStatus") if isinstance(preflight.get("assetStatus"), dict) else {}
    present_slots = _safe_int(asset_status.get("presentSlotCount"))
    referenced_slots = _safe_int(asset_status.get("referencedSlotCount"))
    slot_count = _safe_int(asset_status.get("slotCount"))
    copy_count = _safe_int(preflight.get("copyPlanCount"))
    missing_sources = _safe_int(preflight.get("missingCopySourceCount"))
    stale_runtime = _safe_int(preflight.get("staleRuntimeFileCount"))
    saved_car_count = _safe_int(preflight.get("savedCarCount"))
    rows: list[dict[str, Any]] = []

    rows.append(
        _health_row(
            "external_liveries_root",
            "ok" if preflight.get("externalLiveriesRootExists") else "warning",
            "ExternalLiveries folder",
            "Official Saved Games output folder is available."
            if preflight.get("externalLiveriesRootExists")
            else f"ExternalLiveries folder is not present yet: {preflight.get('externalLiveriesRoot') or '-'}",
            "Create the ExternalLiveries folder before activating the official livery in game.",
            {"path": preflight.get("externalLiveriesRoot") or ""},
        )
    )
    rows.append(
        _health_row(
            "external_livery_json",
            "error" if preflight.get("externalLiveryJsonHasBom") else ("ok" if asset_status.get("externalLiveryJsonExists") else "error"),
            "external_livery.json",
            "external_livery.json has a UTF-8 BOM. AC EVO may ignore this ExternalLiveries folder."
            if preflight.get("externalLiveryJsonHasBom")
            else (
            "Project manifest exists and can be used for official output."
            if asset_status.get("externalLiveryJsonExists")
            else "external_livery.json is missing from the local project folder."
            ),
            "Run Refresh official manifest so LiveryLab rewrites the file as UTF-8 without BOM."
            if preflight.get("externalLiveryJsonHasBom")
            else "Run Refresh official manifest or restore external_livery.json in the project folder.",
            {"path": asset_status.get("externalLiveryJson") or "", "hasBom": bool(preflight.get("externalLiveryJsonHasBom"))},
        )
    )
    rows.append(
        _health_row(
            "linked_saved_car_id",
            "ok" if preflight.get("carGuid") else "error",
            "Linked saved car ID",
            f"Linked car GUID: {preflight.get('carGuid')}"
            if preflight.get("carGuid")
            else "No linked saved car GUID is stored in external_livery.json.",
            "Link a SavedCar before activating the official livery in game.",
            {"carGuid": preflight.get("carGuid") or ""},
        )
    )
    saved_car_deployment = preflight.get("savedCarDeployment") if isinstance(preflight.get("savedCarDeployment"), dict) else {}
    if preflight.get("carGuid"):
        saved_car_ready_local = bool(
            saved_car_deployment.get("managed")
            and saved_car_deployment.get("ready")
            and saved_car_deployment.get("action") == "copy"
        )
        rows.append(
            _health_row(
                "linked_saved_car_file",
                "ok" if saved_car_count > 0 or saved_car_ready_local else "warning",
                "Linked SavedCar file",
                f"{saved_car_count} matching SavedCar file(s) found."
                if saved_car_count > 0
                else (
                    "LiveryLab has prepared a valid SavedCar locally and will install it during Activate in Game."
                    if saved_car_ready_local
                    else "No local SavedCar file matches the linked GUID."
                ),
                "Run Activate in Game to install the prepared SavedCar."
                if saved_car_ready_local
                else "Create or restore the matching in-game saved car, or relink this project.",
                {
                    "savedCars": preflight.get("savedCars") or [],
                    "deployment": saved_car_deployment,
                },
            )
        )
    if saved_car_deployment.get("managed"):
        rows.append(
            _health_row(
                "automatic_saved_car",
                "ok" if saved_car_deployment.get("ready") else "error",
                "Automatic SavedCar",
                "Generated SavedCar is valid and ready for deployment."
                if saved_car_deployment.get("ready")
                else "Generated SavedCar is missing, invalid or has no unambiguous target profile.",
                "Restore the generated project SavedCar or resolve the profile SavedCars folders.",
                saved_car_deployment,
            )
        )
    else:
        template_status = preflight.get("savedCarTemplates") if isinstance(preflight.get("savedCarTemplates"), dict) else {}
        template_count = _safe_int(template_status.get("readyCount"))
        clone_candidate_count = _safe_int(template_status.get("cloneCandidateCount"))
        rows.append(
            _health_row(
                "saved_car_templates",
                "ok" if template_count else "info",
                "SavedCar templates",
                f"{template_count} local SavedCar template(s) available for this car."
                if template_count
                else "No local SavedCar template is available for this car.",
                "Import or register a checked SavedCar template if a package provides one.",
                {
                    "templatesRoot": template_status.get("templatesRoot") or "",
                    "readyCount": template_count,
                    "cloneCandidateCount": clone_candidate_count,
                    "items": template_status.get("items") or [],
                },
            )
        )
    rows.append(
        _health_row(
            "official_png_sources",
            "error" if missing_sources else ("ok" if referenced_slots else "warning"),
            "Official PNG sources",
            f"{referenced_slots} referenced PNG slot(s), {present_slots}/{slot_count} project PNG slot(s) present."
            if referenced_slots
            else "No official PNG texture slots are referenced yet.",
            "Add PNG textures to the project root, then run Refresh official manifest.",
            {
                "presentSlotCount": present_slots,
                "referencedSlotCount": referenced_slots,
                "slotCount": slot_count,
                "missingCopySources": preflight.get("missingCopySources") or [],
            },
        )
    )
    rows.append(
        _health_row(
            "deployment_copy_plan",
            "error" if missing_sources else "ok",
            "Deployment copy plan",
            f"{copy_count} file(s) ready for the official Saved Games target."
            if not missing_sources
            else f"{missing_sources} planned deployment source file(s) are missing.",
            "Restore missing project files before activating or updating in game.",
            {"copyPlan": preflight.get("copyPlan") or []},
        )
    )
    rows.append(
        _health_row(
            "target_folder",
            "info",
            "Saved Games target folder",
            "Target folder exists and can be updated."
            if preflight.get("targetExists")
            else "Target folder does not exist yet and will be created during Activate in Game.",
            "Run Activate in Game when deployment writes are enabled.",
            {"path": preflight.get("targetFolder") or "", "targetHasFiles": bool(preflight.get("targetHasFiles"))},
        )
    )
    if stale_runtime:
        rows.append(
            _health_row(
                "generated_texture_cache",
                "warning",
                "Generated texture cache",
                f"{stale_runtime} generated .texture/.texturemips file(s) already exist for planned PNG output.",
                "Clear generated texture cache when updating PNGs so AC EVO regenerates runtime textures.",
                {"staleRuntimeFiles": preflight.get("staleRuntimeFiles") or []},
            )
        )
    else:
        rows.append(
            _health_row(
                "generated_texture_cache",
                "ok",
                "Generated texture cache",
                "No generated runtime texture files were found for the planned PNG output.",
                "",
                {},
            )
        )

    counts = {"error": 0, "warning": 0, "info": 0, "ok": 0}
    for row in rows:
        counts[row["severity"]] = counts.get(row["severity"], 0) + 1
    status = "ready"
    if counts["error"]:
        status = "blocked"
    elif counts["warning"]:
        status = "warning"
    return {
        "status": status,
        "ready": counts["error"] == 0,
        "summary": (
            "Official project is blocked."
            if counts["error"]
            else ("Official project has warnings." if counts["warning"] else "Official project is ready.")
        ),
        "counts": counts,
        "rows": sorted(rows, key=lambda row: HEALTH_SEVERITY_ORDER.get(row.get("severity"), 2)),
    }


def preflight(
    entry: dict[str, Any],
    project_folder: str | Path,
    *,
    saved_games_ace_root: str | Path | None = None,
    saved_car_templates_root: str | Path | None = None,
    refresh_result: dict[str, Any] | None = None,
    preview_assets_root: str | Path | None = None,
) -> dict[str, Any]:
    folder = Path(project_folder).expanduser()
    if not folder.exists() or not folder.is_dir():
        raise FileNotFoundError(f"Official external project folder not found: {folder}")

    asset_status = (
        refresh_result.get("assetStatus")
        if isinstance(refresh_result, dict) and isinstance(refresh_result.get("assetStatus"), dict)
        else official_external_project_assets.inspect_project(folder)
    )
    external_json_path = folder / official_external_liveries.EXTERNAL_LIVERY_JSON
    external_json_has_bom = official_external_liveries.external_livery_json_has_bom(external_json_path)
    car_guid = str(asset_status.get("carGuid") or entry.get("car_guid") or "").strip()
    external_folder_name = _external_folder_name(entry, folder)
    ace_root = saved_games_paths.ace_root(saved_games_ace_root)
    external_root = project_capabilities.official_external_liveries_root(ace_root)
    target_folder = external_root / external_folder_name
    target_exists = target_folder.exists()
    target_has_files = bool(target_exists and target_folder.is_dir() and any(child.is_file() for child in target_folder.iterdir()))
    copy_rows = _copy_plan(folder, target_folder, asset_status)
    missing_copy_sources = [row for row in copy_rows if not row.get("exists")]
    saved_car_rows = _saved_car_matches(car_guid, ace_root)
    saved_car_deployment = _saved_car_deployment_plan(
        entry,
        folder,
        car_guid,
        ace_root,
        saved_car_rows,
        preview_assets_root,
    )
    saved_car_thumbnail = _saved_car_thumbnail_plan(folder, car_guid, ace_root)
    saved_car_templates = official_saved_car_templates.template_status_for_entry(entry, saved_car_templates_root)
    stale_runtime = _stale_runtime_rows(target_folder, copy_rows) if target_folder.exists() else []
    guid_conflicts = _guid_conflict_rows(external_root, target_folder, car_guid)

    warnings: list[str] = []
    errors: list[str] = []
    if not external_root.exists():
        warnings.append(f"ExternalLiveries root does not exist yet: {external_root}")
    if not asset_status.get("externalLiveryJsonExists"):
        errors.append("external_livery.json is missing")
    if external_json_has_bom:
        errors.append("external_livery.json has a UTF-8 BOM")
    if not car_guid:
        errors.append("car_guid is missing")
    warnings.extend(str(row) for row in (saved_car_deployment.get("warnings") or []) if str(row).strip())
    if saved_car_thumbnail.get("action") in {"invalid", "partial_existing"}:
        warnings.append(str(saved_car_thumbnail.get("reason") or "SavedCar thumbnail could not be prepared"))
    errors.extend(str(row) for row in (saved_car_deployment.get("errors") or []) if str(row).strip())
    if guid_conflicts:
        warnings.append("Other active ExternalLiveries folders already use the same car_guid")
    if missing_copy_sources:
        errors.append("Deployment copy plan contains missing sources")
    if target_exists and not target_folder.is_dir():
        errors.append(f"Deployment target exists but is not a folder: {target_folder}")

    status = "ready"
    if errors:
        status = "blocked"
    elif target_has_files:
        status = "target_exists"
    elif warnings:
        status = "warning"

    result = {
        "status": status,
        "ready": status in {"ready", "warning", "target_exists"},
        "projectFolder": str(folder),
        "savedGamesAceRoot": str(ace_root),
        "externalLiveriesRoot": str(external_root),
        "externalLiveriesRootExists": external_root.exists() and external_root.is_dir(),
        "externalFolderName": external_folder_name,
        "externalLiveryJsonHasBom": external_json_has_bom,
        "targetFolder": str(target_folder),
        "targetExists": target_exists,
        "targetHasFiles": target_has_files,
        "carGuid": car_guid,
        "savedCars": saved_car_rows,
        "savedCarCount": len(saved_car_rows),
        "savedCarDeployment": saved_car_deployment,
        "savedCarThumbnail": saved_car_thumbnail,
        "savedCarTemplates": saved_car_templates,
        "assetStatus": asset_status,
        "copyPlan": copy_rows,
        "copyPlanCount": len(copy_rows),
        "missingCopySources": missing_copy_sources,
        "missingCopySourceCount": len(missing_copy_sources),
        "staleRuntimeFiles": stale_runtime,
        "staleRuntimeFileCount": len(stale_runtime),
        "guidConflictExternalLiveries": guid_conflicts,
        "guidConflictExternalLiveryCount": len(guid_conflicts),
        "warnings": warnings,
        "errors": errors,
    }
    result["health"] = health_from_preflight(result)
    return result
