from __future__ import annotations

import json
import hashlib
import math
import shutil
import struct
from pathlib import Path
from typing import Any

from liverylab_core.proto import is_printable_utf8, parse_fields


ADVANCED_PROJECT_TYPES = {"advanced", "advanced_extracted_content", "legacy", "legacy_advanced"}
SUPPORTED_FORMAT_VERSIONS = {2, 3}
IMPORTED_ADVANCED_PROJECT_DIRNAME = "ImportedAdvancedProject"
BLOCKED_PROJECT_SOURCE_SUFFIXES = {
    ".bat",
    ".cmd",
    ".com",
    ".dll",
    ".exe",
    ".hta",
    ".js",
    ".lnk",
    ".msi",
    ".ps1",
    ".py",
    ".scr",
    ".vbs",
}
EXCLUDED_PROJECT_SOURCE_DIRS = {
    "__pycache__",
    ".cache",
    "cache",
    "code cache",
    "gpucache",
    "layer-studio",
    "tmp",
    "temp",
    "_tmp",
}
SUPPORTED_SLOTS: tuple[tuple[str, str], ...] = (
    ("ext_skin_c", "EXT_Skin_C.png"),
    ("ext_skin_d", "EXT_Skin_D.png"),
    ("ext_skin_r", "EXT_Skin_R.png"),
    ("ext_skin_m", "EXT_Skin_M.png"),
    ("ext_skin_cc", "EXT_Skin_CC.png"),
    ("ext_skin_cr", "EXT_Skin_CR.png"),
)
LEGACY_LINEAR_BC7_SLOTS = {"ext_skin_c", "ext_skin_d"}
THUMBNAIL_NAMES = {"livery_thumbnail.png", "thumbnail.png"}
ADVANCED_METADATA_FIELDS = (
    "tyreBrand",
    "numberplateVariant",
    "numberplateBake",
    "roadPlate",
    "driverProfile",
    "sourceSkin",
)
LEGACY_BASE_PARAMETER_MAP = {
    "Base_Roughness": "roughness",
    "Base_Metalness": "metalness",
    "Base_ClearCoat": "clearcoat",
    "Base_ClearCoatRoughness": "clearcoat_roughness",
    "Base_NormalScale": "normal_scale",
    "Base_ClearCoatNormalScale": "clearcoat_normal_scale",
}
LEGACY_CHANNEL_PARAMETER_MAP = {
    "Roughness": "roughness",
    "Metalness": "metalness",
    "ClearCoat": "clearcoat",
    "ClearCoatRoughness": "clearcoat_roughness",
    "NormalScale": "normal_scale",
    "ClearCoatNormalScale": "clearcoat_normal_scale",
}
OFFICIAL_MATERIAL_CHANNELS = ("red", "green", "blue")
NEUTRAL_BAKED_ALBEDO_COLOR = {"x": 255, "y": 255, "z": 255}


def _clean_type(value: Any) -> str:
    return str(value or "").strip().lower().replace("-", "_")


def official_conversion_policy(package: dict[str, Any] | None) -> dict[str, Any]:
    data = package if isinstance(package, dict) else {}
    package_mode = _clean_type(data.get("packageMode") or "read_only")
    if package_mode == "editable_project":
        return {
            "allowed": True,
            "packageMode": package_mode,
            "targetRole": "project",
            "reason": "Editable Advanced projects can be converted into editable Official projects.",
        }
    if package_mode == "read_only":
        return {
            "allowed": True,
            "packageMode": package_mode,
            "targetRole": "library",
            "reason": "Read-only Advanced packages can be converted only into read-only Official library liveries.",
        }
    return {
        "allowed": False,
        "packageMode": package_mode,
        "targetRole": "",
        "reason": f"Unsupported Advanced package mode for Official conversion: {package_mode or 'missing'}",
    }


def as_read_only_official_library_entry(entry: dict[str, Any], local_root: str | Path) -> dict[str, Any]:
    root = Path(local_root).expanduser()
    data = dict(entry)
    data["local_root"] = str(root)
    data["origin"] = "package_import"
    data["project_state"] = "none"
    data["editable_project"] = False
    data["official_external_role"] = "library"
    data["storage_role"] = "livery_library"
    data["active"] = False

    files = dict(data.get("files") or {}) if isinstance(data.get("files"), dict) else {}
    details = dict(data.get("official_external") or {}) if isinstance(data.get("official_external"), dict) else {}
    details.update({
        "role": "library",
        "roleLabel": "Installed library copy",
        "storageRole": "livery_library",
        "deploymentTarget": "saved_games_external_liveries",
        "deploymentStatus": "package_not_deployed",
        "deployed": False,
        "savedCarFound": False,
        "savedCarPaths": [],
        "deploymentTargetFolder": "",
    })
    generated = dict(details.get("generatedSavedCar") or {}) if isinstance(details.get("generatedSavedCar"), dict) else {}
    relative_text = str(generated.get("relativePath") or files.get("saved_car") or "").strip()
    if relative_text:
        relative = Path(*[part for part in relative_text.replace("\\", "/").split("/") if part])
        generated["relativePath"] = relative.as_posix()
        generated["path"] = str(root / relative)
        generated["deploymentStatus"] = "prepared_local" if (root / relative).is_file() else "missing_local"
        generated["deployedPath"] = ""
        generated.pop("deployedAt", None)
        details["generatedSavedCar"] = generated
        files["saved_car"] = relative.as_posix()
    data["files"] = files
    data["official_external"] = details

    for key in ("project_thumbnail_disk_path", "thumbnail_png_disk_path"):
        old = str(data.get(key) or "").strip()
        if old:
            candidate = root / Path(old).name
            data[key] = str(candidate) if candidate.is_file() else ""
    return data


def legacy_texture_decode_format(slot_id: Any, block_bytes: int | None = None) -> str:
    slot = _clean_type(slot_id)
    if slot in LEGACY_LINEAR_BC7_SLOTS and block_bytes == 16:
        return "bc7"
    return "auto"


def select_compatible_official_variant(
    variants: list[dict[str, Any]],
    *,
    requested_id: str = "",
    default_id: str = "",
    preferred_mechanical: str = "",
) -> dict[str, Any]:
    rows = [dict(row) for row in variants if isinstance(row, dict)]
    compatible = [row for row in rows if row.get("officialExternalCompatible") is True]
    if not compatible:
        return {}
    normalize = lambda value: str(value or "").replace("/", "\\").strip().lower()
    requested = next((row for row in rows if str(row.get("id") or row.get("key") or "") == requested_id), None) if requested_id else None
    target_mechanical = normalize((requested or {}).get("mechanicalPreset") or preferred_mechanical)
    compatible.sort(key=lambda row: (
        0 if target_mechanical and normalize(row.get("mechanicalPreset")) == target_mechanical else 1,
        0 if "EXT_SKIN" in ((row.get("officialExternalCompatibility") or {}).get("neutralMaterialStems") or []) else 1,
        0 if str(row.get("id") or row.get("key") or "") == default_id else 1,
        str(row.get("id") or row.get("key") or "").lower(),
    ))
    if requested and requested.get("officialExternalCompatible") is True:
        return requested
    selected = compatible[0]
    fallback_from = requested_id or (default_id if default_id and str(selected.get("id") or selected.get("key") or "") != default_id else "")
    return {**selected, **({"compatibilityFallbackFrom": fallback_from} if fallback_from else {})}


def _read_manifest(package_root: Path) -> dict[str, Any]:
    path = package_root / "manifest.json"
    if not path.is_file():
        raise FileNotFoundError("Legacy package manifest.json is missing")
    data = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(data, dict):
        raise ValueError("Legacy package manifest is invalid")
    return data


def _first_named_file(root: Path, names: set[str]) -> Path | None:
    if not root.exists() or not root.is_dir():
        return None
    wanted = {name.lower() for name in names}
    candidates = [path for path in root.rglob("*") if path.is_file() and path.name.lower() in wanted]
    candidates.sort(key=lambda path: (len(path.relative_to(root).parts), path.as_posix().lower()))
    return candidates[0] if candidates else None


def _package_member(package_root: Path, value: Any) -> Path | None:
    text = str(value or "").strip().replace("\\", "/")
    if not text:
        return None
    candidate = (package_root / Path(text)).resolve()
    try:
        candidate.relative_to(package_root.resolve())
    except ValueError:
        return None
    return candidate


def _fixed32_float(raw: bytes) -> float:
    return float(struct.unpack("<f", raw)[0])


def _decode_material_value(raw: bytes) -> Any:
    if not raw:
        return None
    try:
        fields = parse_fields(raw)
    except Exception:
        return None
    if len(fields) == 1 and fields[0].number == 1 and fields[0].wire == 5:
        return _fixed32_float(fields[0].value)
    if len(fields) == 1 and fields[0].number == 2 and fields[0].wire == 2:
        return is_printable_utf8(fields[0].value)
    if len(fields) == 1 and fields[0].wire == 2:
        try:
            nested = parse_fields(fields[0].value)
        except Exception:
            return None
        values = [_fixed32_float(field.value) for field in nested if field.wire == 5 and isinstance(field.value, bytes) and len(field.value) == 4]
        if values:
            return values
    return None


def _material_entry(raw: bytes) -> tuple[str, Any]:
    name = ""
    value: Any = None
    for field in parse_fields(raw):
        if field.number == 1 and field.wire == 2 and isinstance(field.value, bytes):
            name = str(is_printable_utf8(field.value) or "")
        elif field.number == 2 and field.wire == 2 and isinstance(field.value, bytes):
            value = _decode_material_value(field.value)
        elif field.number == 2 and field.wire == 0:
            value = int(field.value)
    return name, value


def _linear_to_srgb_byte(value: Any) -> int:
    try:
        linear = max(0.0, min(1.0, float(value)))
    except (TypeError, ValueError):
        return 0
    srgb = linear * 12.92 if linear <= 0.0031308 else 1.055 * math.pow(linear, 1.0 / 2.4) - 0.055
    return max(0, min(255, int(round(srgb * 255.0))))


def inspect_legacy_material(package_root: str | Path, manifest: dict[str, Any]) -> dict[str, Any]:
    root = Path(package_root).resolve()
    copied = manifest.get("copied") if isinstance(manifest.get("copied"), list) else []
    material_row = next((row for row in copied if isinstance(row, dict) and _clean_type(row.get("kind")) == "material"), None)
    material_path = _package_member(root, material_row.get("path")) if material_row else None
    if material_path is None or not material_path.is_file():
        return {"ready": False, "reason": "Legacy material file is not included in the package", "path": "", "parameters": {}}
    parameters: dict[str, Any] = {}
    try:
        for field in parse_fields(material_path.read_bytes()):
            if field.number != 4 or field.wire != 2 or not isinstance(field.value, bytes):
                continue
            name, value = _material_entry(field.value)
            if name:
                parameters[name] = value
    except Exception as exc:
        return {"ready": False, "reason": f"Legacy material could not be parsed: {exc}", "path": str(material_path), "parameters": {}}
    base_color = parameters.get("Base_Basecolor")
    color_values = base_color if isinstance(base_color, list) and len(base_color) >= 3 else []
    official_channel: dict[str, Any] = {}
    if color_values:
        rgb = [_linear_to_srgb_byte(value) for value in color_values[:3]]
        official_channel["base_color"] = {"x": rgb[0], "y": rgb[1], "z": rgb[2]}
        official_channel["baseColorHex"] = "#" + "".join(f"{value:02x}" for value in rgb)
    for legacy_name, official_name in LEGACY_BASE_PARAMETER_MAP.items():
        value = parameters.get(legacy_name)
        if isinstance(value, (int, float)) and math.isfinite(float(value)):
            official_channel[official_name] = max(0.0, min(1.0, float(value)))
    official_channels: dict[str, dict[str, Any]] = {}
    for channel_name in OFFICIAL_MATERIAL_CHANNELS:
        prefix = channel_name.capitalize()
        channel_color = parameters.get(f"{prefix}_Basecolor")
        values = channel_color if isinstance(channel_color, list) and len(channel_color) >= 3 else []
        if not values:
            continue
        rgb = [_linear_to_srgb_byte(value) for value in values[:3]]
        channel: dict[str, Any] = {
            "base_color": {"x": rgb[0], "y": rgb[1], "z": rgb[2]},
            "baseColorHex": "#" + "".join(f"{value:02x}" for value in rgb),
        }
        for legacy_suffix, official_name in LEGACY_CHANNEL_PARAMETER_MAP.items():
            value = parameters.get(f"{prefix}_{legacy_suffix}")
            if isinstance(value, (int, float)) and math.isfinite(float(value)):
                channel[official_name] = max(0.0, min(1.0, float(value)))
        official_channels[channel_name] = channel
    fidelity_features = sorted({
        feature
        for feature, markers in {
            "anisotropy": ("Anisotropy",),
            "detail material": ("Detail_",),
            "micro shadows": ("MicroShadows",),
            "splat blending": ("Splatmap_",),
        }.items()
        if any(marker in name and value not in (None, 0, 0.0, "") for name, value in parameters.items() for marker in markers)
    })
    return {
        "ready": bool(official_channel.get("base_color")),
        "reason": "Legacy base material parameters parsed" if official_channel.get("base_color") else "Legacy Base_Basecolor is unavailable",
        "path": str(material_path),
        "parameters": {key: parameters.get(key) for key in ["Base_Basecolor", *LEGACY_BASE_PARAMETER_MAP.keys()] if parameters.get(key) is not None},
        "officialPrimaryChannel": official_channel,
        "officialChannels": official_channels,
        "fidelityWarnings": [
            f"Official External Liveries cannot reproduce every Advanced {feature} parameter exactly."
            for feature in fidelity_features
        ],
    }


def inspect_detail_map_neutrality(
    detail_png_path: str | Path,
    *,
    compression_noise_ceiling: int = 16,
    authored_pixel_ratio_ceiling: float = 0.001,
) -> dict[str, Any]:
    path = Path(detail_png_path)
    if not path.is_file():
        return {
            "neutral": False,
            "reason": "Legacy detail map is missing",
            "path": str(path),
            "maximumChannelValue": None,
            "compressionNoiseCeiling": int(compression_noise_ceiling),
            "authoredPixelRatioCeiling": float(authored_pixel_ratio_ceiling),
        }
    try:
        from PIL import Image, ImageChops

        with Image.open(path) as image:
            normalized = image.convert("RGB")
            extrema = normalized.getextrema()
            masks = [
                channel.point(lambda value: 255 if value > int(compression_noise_ceiling) else 0)
                for channel in normalized.split()
            ]
            authored_mask = ImageChops.lighter(ImageChops.lighter(masks[0], masks[1]), masks[2])
            histogram = authored_mask.histogram()
            pixel_count = int(normalized.width) * int(normalized.height)
            authored_pixel_count = pixel_count - int(histogram[0])
    except Exception as exc:
        return {
            "neutral": False,
            "reason": f"Legacy detail map could not be inspected: {exc}",
            "path": str(path),
            "maximumChannelValue": None,
            "compressionNoiseCeiling": int(compression_noise_ceiling),
            "authoredPixelRatioCeiling": float(authored_pixel_ratio_ceiling),
        }
    maximum = max(int(channel[1]) for channel in extrema[:3]) if extrema else 255
    authored_ratio = (authored_pixel_count / pixel_count) if pixel_count else 1.0
    neutral = (
        maximum <= int(compression_noise_ceiling)
        or authored_ratio <= float(authored_pixel_ratio_ceiling)
    )
    return {
        "neutral": neutral,
        "reason": (
            "Detail map is neutral black apart from compression noise"
            if neutral
            else "Detail map contains authored splat-channel values"
        ),
        "path": str(path),
        "maximumChannelValue": maximum,
        "compressionNoiseCeiling": int(compression_noise_ceiling),
        "authoredPixelCount": authored_pixel_count,
        "pixelCount": pixel_count,
        "authoredPixelRatio": authored_ratio,
        "authoredPixelRatioCeiling": float(authored_pixel_ratio_ceiling),
        "channelExtrema": [list(channel) for channel in extrema[:3]],
    }


def apply_baked_albedo_material(external_json_path: str | Path, material: dict[str, Any]) -> dict[str, Any]:
    path = Path(external_json_path)
    channel_values = material.get("officialPrimaryChannel") if isinstance(material.get("officialPrimaryChannel"), dict) else {}
    if not path.is_file():
        return {"changed": False, "reason": "Official external_livery.json is missing", "path": str(path)}
    data = json.loads(path.read_text(encoding="utf-8"))
    materials = data.get("materials") if isinstance(data.get("materials"), list) else []
    target = next((row for row in materials if isinstance(row, dict) and str(row.get("batch_name") or "").upper().startswith("EXT_SKIN")), None)
    if target is None:
        return {"changed": False, "reason": "Official EXT_SKIN material batch is missing", "path": str(path)}
    before = json.dumps(target, sort_keys=True)
    applied: dict[str, Any] = {}
    finish_values = {
        key: value
        for key, value in channel_values.items()
        if key not in {"base_color", "baseColorHex"}
    }
    for channel_name in OFFICIAL_MATERIAL_CHANNELS:
        channel = target.get(channel_name) if isinstance(target.get(channel_name), dict) else {}
        channel["base_color"] = dict(NEUTRAL_BAKED_ALBEDO_COLOR)
        channel.update(finish_values)
        target[channel_name] = channel
        applied[channel_name] = dict(channel)
    changed = json.dumps(target, sort_keys=True) != before
    if changed:
        path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
    return {
        "changed": changed,
        "reason": (
            "Official material channels neutralized for baked Legacy albedo"
            if changed
            else "Official material channels already preserve the baked Legacy albedo"
        ),
        "path": str(path),
        "channels": applied,
        "neutralBaseColor": dict(NEUTRAL_BAKED_ALBEDO_COLOR),
        "finishValues": finish_values,
    }


def apply_legacy_material_channels(external_json_path: str | Path, material: dict[str, Any]) -> dict[str, Any]:
    path = Path(external_json_path)
    source_channels = material.get("officialChannels") if isinstance(material.get("officialChannels"), dict) else {}
    if not path.is_file():
        return {"changed": False, "reason": "Official external_livery.json is missing", "path": str(path)}
    if not source_channels:
        return {"changed": False, "reason": "Legacy RGB material channels are unavailable", "path": str(path)}
    data = json.loads(path.read_text(encoding="utf-8"))
    materials = data.get("materials") if isinstance(data.get("materials"), list) else []
    target = next((row for row in materials if isinstance(row, dict) and str(row.get("batch_name") or "").upper().startswith("EXT_SKIN")), None)
    if target is None:
        return {"changed": False, "reason": "Official EXT_SKIN material batch is missing", "path": str(path)}
    before = json.dumps(target, sort_keys=True)
    applied: dict[str, Any] = {}
    for channel_name in OFFICIAL_MATERIAL_CHANNELS:
        source = source_channels.get(channel_name) if isinstance(source_channels.get(channel_name), dict) else {}
        if not source:
            continue
        channel = target.get(channel_name) if isinstance(target.get(channel_name), dict) else {}
        for key, value in source.items():
            if key == "baseColorHex":
                continue
            channel[key] = dict(value) if isinstance(value, dict) else value
        target[channel_name] = channel
        applied[channel_name] = dict(channel)
    changed = json.dumps(target, sort_keys=True) != before
    if changed:
        path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
    return {
        "changed": changed,
        "reason": "Legacy RGB and PBR material channels migrated" if changed else "Legacy material channels are already current",
        "path": str(path),
        "channels": applied,
        "fidelityWarnings": list(material.get("fidelityWarnings") or []),
    }


def _slot_source(package_root: Path, project_root: Path | None, slot_id: str, png_name: str) -> dict[str, Any]:
    png_names = {png_name, f"{slot_id}.png"}
    if project_root:
        png = _first_named_file(project_root, png_names)
        if png:
            return {"slotId": slot_id, "filename": png_name, "sourceType": "png", "sourcePath": str(png), "ready": True}
    texture = _first_named_file(package_root / "content", {f"{slot_id}.texture"})
    mips = texture.with_suffix(".texturemips") if texture else None
    if texture and mips and mips.is_file():
        return {
            "slotId": slot_id,
            "filename": png_name,
            "sourceType": "texture",
            "sourcePath": str(texture),
            "sourceMipsPath": str(mips),
            "ready": True,
        }
    return {"slotId": slot_id, "filename": png_name, "sourceType": "", "sourcePath": "", "ready": False}


def package_content_fingerprint(
    *,
    car_model: str,
    source_skin_id: str,
    slots: list[dict[str, Any]],
    legacy_material: dict[str, Any] | None = None,
    layer_studio_root: str | Path | None = None,
) -> str:
    digest = hashlib.sha256()

    def add_text(label: str, value: Any) -> None:
        digest.update(label.encode("utf-8"))
        digest.update(b"\0")
        digest.update(str(value or "").strip().lower().encode("utf-8"))
        digest.update(b"\0")

    def add_file(label: str, path_value: str | Path) -> None:
        path = Path(path_value)
        if not path.is_file():
            return
        add_text("file", label)
        with path.open("rb") as handle:
            for chunk in iter(lambda: handle.read(1024 * 1024), b""):
                digest.update(chunk)
        digest.update(b"\0")

    add_text("schema", 1)
    add_text("car", car_model)
    add_text("skin", source_skin_id)
    for row in sorted((row for row in slots if isinstance(row, dict) and row.get("ready")), key=lambda item: str(item.get("slotId") or "")):
        slot_id = str(row.get("slotId") or "").strip().lower()
        source = Path(str(row.get("sourcePath") or ""))
        add_file(f"slot:{slot_id}", source)
        if str(row.get("sourceType") or "").strip().lower() == "texture":
            add_file(f"slot:{slot_id}:mips", row.get("sourceMipsPath") or source.with_suffix(".texturemips"))
    material = legacy_material if isinstance(legacy_material, dict) else {}
    material_path = str(material.get("path") or "").strip()
    if material_path:
        add_file("legacy-material", material_path)
    layer_root = Path(layer_studio_root) if layer_studio_root else None
    if layer_root and layer_root.is_dir():
        for path in sorted((path for path in layer_root.rglob("*") if path.is_file()), key=lambda item: item.relative_to(layer_root).as_posix().lower()):
            add_file(f"layer:{path.relative_to(layer_root).as_posix().lower()}", path)
    return digest.hexdigest()


def inspect_package_root(package_root: str | Path) -> dict[str, Any]:
    root = Path(package_root).resolve()
    manifest = _read_manifest(root)
    skin = manifest.get("skin") if isinstance(manifest.get("skin"), dict) else {}
    project = manifest.get("project") if isinstance(manifest.get("project"), dict) else {}
    dependencies = manifest.get("dependencies") if isinstance(manifest.get("dependencies"), dict) else {}
    try:
        format_version = int(manifest.get("formatVersion") or 0)
    except (TypeError, ValueError):
        format_version = 0
    declared_project_type = _clean_type(manifest.get("projectType") or skin.get("projectType"))
    project_type = declared_project_type
    project_type_inferred = False
    project_type_reason = "manifest"
    if not project_type:
        copied = manifest.get("copied") if isinstance(manifest.get("copied"), list) else []
        copied_kinds = {_clean_type(row.get("kind")) for row in copied if isinstance(row, dict)}
        copied_paths = " ".join(str(row.get("path") or "").replace("/", "\\").lower() for row in copied if isinstance(row, dict))
        official_details = manifest.get("officialExternal") or skin.get("officialExternal")
        if official_details or "content\\official_external_liveries\\" in copied_paths:
            project_type = "official_external_livery"
            project_type_inferred = True
            project_type_reason = "Official external package structure"
        elif copied_kinds & {"cardesign", "design", "skin_folder", "material", "visual_preset", "mechanical_preset", "carfinalstate"}:
            project_type = "advanced_extracted_content"
            project_type_inferred = True
            project_type_reason = "Legacy Advanced content entries"
    package_mode = _clean_type(manifest.get("packageMode") or "read_only")
    blockers: list[str] = []
    warnings: list[str] = []
    if format_version not in SUPPORTED_FORMAT_VERSIONS:
        blockers.append(f"Unsupported legacy package format version: {format_version or 'missing'}")
    if project_type not in ADVANCED_PROJECT_TYPES:
        blockers.append(f"Package is not an Advanced legacy livery: {project_type or 'unknown project type'}")
    car_model = str(skin.get("carModel") or "").strip()
    if not car_model:
        blockers.append("Legacy package does not identify its target car")

    project_root: Path | None = None
    if package_mode == "editable_project" or bool(project.get("included")):
        project_rel = str(project.get("path") or "project").strip() or "project"
        candidate = (root / Path(project_rel.replace("\\", "/"))).resolve()
        try:
            candidate.relative_to(root)
        except ValueError:
            blockers.append("Editable project path escapes the package root")
        else:
            if candidate.is_dir():
                project_root = candidate
            else:
                blockers.append("Editable legacy package is missing its project folder")

    slots = [_slot_source(root, project_root, slot_id, filename) for slot_id, filename in SUPPORTED_SLOTS]
    primary = next((row for row in slots if row["slotId"] == "ext_skin_c"), {})
    if not primary.get("ready"):
        blockers.append("Legacy package does not contain a usable EXT_Skin_C source")
    ready_slots = [row for row in slots if row.get("ready")]

    thumbnail = _first_named_file(project_root, THUMBNAIL_NAMES) if project_root else None
    if thumbnail is None:
        thumbnail = _first_named_file(root / "content", THUMBNAIL_NAMES)
    layer_studio_root = project_root / "layer-studio" if project_root and (project_root / "layer-studio").is_dir() else None

    dropped: list[dict[str, Any]] = []
    for field in ADVANCED_METADATA_FIELDS:
        value = skin.get(field)
        present = bool(value) if not isinstance(value, dict) else bool(value)
        if present:
            dropped.append({"field": field, "reason": "Advanced-only metadata is not imported into Official External Liveries"})
    dependency_counts = {
        key: len(value) if isinstance(value, list) else 0
        for key, value in dependencies.items()
    }
    if sum(dependency_counts.values()):
        warnings.append("Bundled Advanced dependencies will be ignored during Official import")
    manual_review = bool(skin.get("numberplateVariant") or skin.get("numberplateBake") or skin.get("roadPlate"))
    if manual_review:
        warnings.append("Manual texture review required: numberplate or road-plate graphics may already be baked into EXT_Skin_C")
    legacy_material = inspect_legacy_material(root, manifest)
    if not legacy_material.get("ready"):
        warnings.append(str(legacy_material.get("reason") or "Legacy material parameters are unavailable"))

    source_skin_id = str(skin.get("skinId") or "").strip()
    content_fingerprint = package_content_fingerprint(
        car_model=car_model,
        source_skin_id=source_skin_id,
        slots=slots,
        legacy_material=legacy_material,
        layer_studio_root=layer_studio_root,
    )

    return {
        "schemaVersion": 1,
        "ready": not blockers,
        "blockers": blockers,
        "warnings": warnings,
        "packageRoot": str(root),
        "formatVersion": format_version,
        "packageMode": package_mode,
        "officialConversionPolicy": official_conversion_policy({"packageMode": package_mode}),
        "projectType": project_type,
        "declaredProjectType": declared_project_type,
        "projectTypeInferred": project_type_inferred,
        "projectTypeReason": project_type_reason,
        "skin": skin,
        "carModel": car_model,
        "sourceSkinId": source_skin_id,
        "contentFingerprint": content_fingerprint,
        "displayName": str(skin.get("displayName") or skin.get("skinId") or "Imported Official Livery").strip(),
        "author": str(skin.get("author") or "").strip(),
        "description": str(skin.get("description") or "").strip(),
        "projectRoot": str(project_root) if project_root else "",
        "slots": slots,
        "readySlotCount": len(ready_slots),
        "thumbnailPath": str(thumbnail) if thumbnail else "",
        "layerStudioRoot": str(layer_studio_root) if layer_studio_root else "",
        "layerStudioAvailable": bool(layer_studio_root),
        "droppedFeatures": dropped,
        "dependencyCounts": dependency_counts,
        "manualTextureReview": manual_review,
        "legacyMaterial": legacy_material,
    }


def copy_layer_studio_sources(source_root: str | Path, target_root: str | Path) -> dict[str, Any]:
    target = Path(target_root)
    source_text = str(source_root or "").strip()
    if not source_text:
        return {"copied": False, "fileCount": 0, "surfaces": []}
    source = Path(source_text).expanduser()
    if not source.is_dir():
        return {"copied": False, "fileCount": 0, "surfaces": []}
    allowed = {slot_id for slot_id, _filename in SUPPORTED_SLOTS}
    allowed.update({"project-library"})
    file_count = 0
    surfaces: list[str] = []
    for child in sorted(source.iterdir(), key=lambda path: path.name.lower()):
        if not child.is_dir() or child.name.lower() not in allowed:
            continue
        destination = target / child.name
        if destination.exists():
            shutil.rmtree(destination)
        shutil.copytree(child, destination)
        file_count += sum(1 for path in destination.rglob("*") if path.is_file())
        surfaces.append(child.name)
    return {"copied": bool(file_count), "fileCount": file_count, "surfaces": surfaces}


def copy_legacy_project_sources(source_root: str | Path, official_project_root: str | Path) -> dict[str, Any]:
    official_root = Path(official_project_root).expanduser()
    target = official_root / IMPORTED_ADVANCED_PROJECT_DIRNAME
    source_text = str(source_root or "").strip()
    if not source_text:
        return {
            "copied": False,
            "sourceRoot": "",
            "targetRoot": str(target),
            "fileCount": 0,
            "bytes": 0,
            "skipped": [],
        }
    source = Path(source_text).expanduser()
    if not source.is_dir():
        return {
            "copied": False,
            "sourceRoot": str(source),
            "targetRoot": str(target),
            "fileCount": 0,
            "bytes": 0,
            "skipped": [],
        }
    copied: list[str] = []
    skipped: list[str] = []
    total_bytes = 0
    for child in sorted(
        (item for item in source.rglob("*") if item.is_file()),
        key=lambda item: item.relative_to(source).as_posix().lower(),
    ):
        relative = child.relative_to(source)
        if any(part.lower() in EXCLUDED_PROJECT_SOURCE_DIRS for part in relative.parts[:-1]):
            continue
        suffix = child.suffix.lower()
        if suffix in BLOCKED_PROJECT_SOURCE_SUFFIXES:
            skipped.append(relative.as_posix())
            continue
        destination = target / relative
        destination.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(child, destination)
        copied.append(relative.as_posix())
        total_bytes += child.stat().st_size
    return {
        "copied": bool(copied),
        "sourceRoot": str(source),
        "targetRoot": str(target),
        "fileCount": len(copied),
        "bytes": total_bytes,
        "files": copied,
        "skipped": skipped[:100],
        "skippedCount": len(skipped),
    }
