#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
LiveryLab v1.2.0

Integrated tool for extracted Assetto Corsa EVO content:
- .texture + .texturemips <-> DDS conversion with tiled mip support
- material inspection and referenced texture export
- root file browser
- new skin creator from existing .cardesign/.design templates, including .actor registration
- actor viewer for cardesign registry
- design/cardesign viewer and editor
- automatic backups before overwriting existing files

Work in a copy of extracted content.
"""
from __future__ import annotations

import base64
import copy
import csv
import hashlib
import io
import json
import math
import os
import queue
import re
import shutil
import struct
import subprocess
import sys
import tempfile
import threading
import time
import traceback
import uuid
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Any, Callable, Iterable

from liverylab_core.proto import (
    ProtoField,
    _normalize_exact_text,
    build_fields,
    encode_varint,
    is_printable_utf8,
    parse_fields,
    patch_strings_recursive,
    patch_strings_recursive_exact,
    read_varint,
)
from liverylab_core import ace_grid_cache, advanced_content_setup, advanced_creator_catalog, advanced_numberplate_state, advanced_to_official_import, ai_vehicle_spec, back_to_default, default_racing_driver, driver_library, driver_preview, driver_projects, driver_skin, evo_update_workflow, export_sanitize, ext_banner, game_launcher, generated_readmes, grid_bridge, grid_presets, install_relocation, kspkg_extract, layer_studio, layer_studio_color_favorites, livery_categories, livery_identity, livery_start_points, livery_template_preview, local_storage_cleanup, multiplayer_safety, official_car_resources, official_external_deployment, official_external_library, official_external_liveries, official_external_project_assets, official_external_projects, official_external_registration, official_external_workflow, official_material_surfaces, official_package_portability, official_package_validation, official_preview_pipeline, official_preview_storage, official_saved_car_generation, official_saved_car_state, official_saved_car_templates, official_saved_car_vault, official_saved_cars, official_storage, official_to_advanced_import, official_variant_compatibility, official_variant_parts, online_league, online_league_deployments, package_refresh, package_security, package_state, package_zip, paint_only_livery, preview3d, preview3d_textures, project_capabilities, project_types, road_plates, runtime_profiles, runtime_switch, runtime_write_guards, saved_car_body_color, saved_games_paths, skin_package_dependencies, skin_policy, texture_descriptors, uv_templates, variant_parts, workflow_scopes
from liverylab_core import numberplates as numberplate_core
from liverylab_core import legacy_official_migration
from liverylab_core import migrated_livery_trash
from liverylab_core import numberplate_render
from liverylab_core import numberplate_templates as numberplate_template_core
from liverylab_core.car_analysis import IGNORED_CAR_DIRS, analyze_cars
from liverylab_core.game_file_writer import GameFileWriter
from liverylab_core.file_watcher import PngFileWatcher
from liverylab_core.game_write_guard import require_game_write_ready
from liverylab_core.process import run_cli_hidden
try:
    import tkinter as tk
    from tkinter import filedialog, messagebox, ttk
    TK_AVAILABLE = True
except ModuleNotFoundError:
    TK_AVAILABLE = False

    class _MissingTk:
        class Tk:
            pass

        def __getattr__(self, name):
            raise RuntimeError("Tkinter is not available in this Python environment.")

    tk = _MissingTk()
    filedialog = messagebox = ttk = _MissingTk()

APP_NAME = "LiveryLab"
DEBUG_TOOLS_ARG = "--debug-tools"
PREVIEW3D_SCENE_NAMES = ("light", "dark", "studio")
RACING_GENERIC_SLICK_TYRETEXTURES_REF = "content\\cars\\common_assets\\parts\\tyres\\textures\\racing_generic_1\\tyre_racing_generic_1.tyretextures"
RACING_GENERIC_WET_TYRETEXTURES_REF = "content\\cars\\common_assets\\parts\\tyres\\textures\\racing_generic_w_1\\tyre_racing_generic_w_1.tyretextures"
TYRE_BRAND_TEXTURE_OVERRIDES: dict[str, dict[str, Any]] = {
    "racing_michelin": {
        "label": "Michelin",
        "brand": "michelin",
        "family": "racing",
        "aliases": ["michelin"],
        "refs": [
            "content\\cars\\common_assets\\parts\\tyres\\textures\\racing_cup_1\\tyre_racing_cup_1.tyretextures",
            "content\\cars\\common_assets\\parts\\tyres\\textures\\racing_cup_w_1\\tyre_racing_cup_w_1.tyretextures",
        ],
        "fallback": RACING_GENERIC_SLICK_TYRETEXTURES_REF,
    },
    "racing_pirelli": {
        "label": "Pirelli",
        "brand": "pirelli",
        "family": "racing",
        "aliases": ["pirelli"],
        "refs": [
            "content\\cars\\common_assets\\parts\\tyres\\textures\\racing_dhf_1\\tyre_racing_dhf_1.tyretextures",
            "content\\cars\\common_assets\\parts\\tyres\\textures\\racing_whb_1\\tyre_racing_whb_1.tyretextures",
        ],
        "fallback": RACING_GENERIC_SLICK_TYRETEXTURES_REF,
    },
    "racing_dunlop": {
        "label": "Dunlop",
        "brand": "dunlop",
        "family": "racing",
        "aliases": ["dunlop"],
        "refs": [
            "content\\cars\\common_assets\\parts\\tyres\\textures\\racing_dunlop_1\\tyre_racing_dunlop_1.tyretextures",
            "content\\cars\\common_assets\\parts\\tyres\\textures\\racing_dunlop_w_1\\tyre_racing_dunlop_w_1.tyretextures",
        ],
        "fallback": RACING_GENERIC_SLICK_TYRETEXTURES_REF,
    },
}


def _read_app_version() -> str:
    module_root = Path(__file__).resolve().parent
    candidates = [module_root / "VERSION"]
    if module_root.name == "_internal":
        candidates.append(module_root.parent / "VERSION")
    for path in candidates:
        try:
            version = path.read_text(encoding="utf-8").strip()
        except OSError:
            continue
        if version:
            return version if version.startswith("v") else f"v{version}"
    return "v0.18.0"


APP_VERSION = _read_app_version()
SKIN_THUMBNAIL_FILENAME = "livery_thumbnail.png"
LIVERY_PROJECT_README_FILENAME = "README.txt"
LIVERY_PACKAGE_EXPORT_DIRNAME = "LiveryPackageExport"
LEGACY_LIVERY_PACKAGE_EXPORT_DIRNAMES = ["LiveryPackages", "CustomLiveries"]
LIVERY_PROJECTS_DIRNAME = "LiveryProjects"
LIVERY_LIBRARY_DIRNAME = "LiveryLibrary"
USER_DATA_DIRNAME = "UserData"
WEBVIEW_USER_DATA_DIRNAME = "WebView"
LOCAL_SKINS_DIRNAME = "LocalSkins"
MY_SKIN_MANAGED_BY_LIVERYLAB = "liverylab"
MY_SKIN_MANAGED_BY_GAME_IMPORT = "game_import"
MY_SKIN_PREVIEW_SOURCE_AUTO = "auto"
MY_SKIN_PREVIEW_SOURCE_CUSTOM = "custom"
MY_SKIN_PREVIEW_SOURCE_ACE = "ace"
TYRE_BRANDS_DIRNAME = "TyreBrands"
GRID_PRESETS_DIRNAME = "GridPresets"
GAME_FILE_BACKUPS_DIRNAME = "GameFileBackups"
EXTRACTED_TOP_LEVEL_DIR_NAMES = ("cfg", "content", "editor", "serverconfig", "system", "uiresources")
MY_SKINS_REGISTRY_FILENAME = "my_skins.json"
SETTINGS_FILENAME = "settings.json"
OFFICIAL_EXTERNAL_SUPPRESSED_SOURCE_IDS_SETTINGS_KEY = "official_external_suppressed_source_ids"
SKIN_METADATA_FILENAME = "liverylab_skin.json"
CONVERTED_DIRNAME = "Converted"
CONVERTED_TEXTURES_DIRNAME = "Textures"
CONVERTED_MESHES_DIRNAME = "Meshes"
CONVERTED_UV_DIRNAME = "UV"
CONVERTED_UNASSIGNED_DIRNAME = "Unassigned"
TRASH_DIRNAME = "LiveryLab_Trash"
PACK_KEY = 0x9F9721A97D1135C1
PACK_FILE_TABLE_SIZE = 0x2000000
PACK_ENTRY_SIZE = 0x100
PACK_MAX_FILE_COUNT = PACK_FILE_TABLE_SIZE // PACK_ENTRY_SIZE
PACK_ENTRY_STRUCT = struct.Struct("<224sIHHQQQ")
PACK_TABLE_TRAILER_OFFSETS = (PACK_FILE_TABLE_SIZE, PACK_FILE_TABLE_SIZE * 2)
PACK_EXTRACT_CHUNK_SIZE = 16 * 1024 * 1024
PACK_PROGRESS_INTERVAL_SECONDS = 0.25

# Optional hardcoded default. Leave empty to use the saved setting instead.
# Example: DEFAULT_ROOT_FOLDER = r"D:\\ACEVO_EXTRACTED"
DEFAULT_ROOT_FOLDER = ""
LIVERY_PREVIEW_WATCHER = PngFileWatcher()
DRIVER_PREVIEW_WATCHER = PngFileWatcher()
MY_SKIN_THUMBNAIL_WATCHER = PngFileWatcher()
LIVERY_PROJECT_WORK_EXTENSIONS = {".psd", ".psb", ".xcf", ".afphoto", ".kra", ".ai", ".svg"}
LIVERY_PROJECT_MANAGED_FILENAMES = {
    LIVERY_PROJECT_README_FILENAME.lower(),
    "preview_project.json",
    "livery_thumbnail.png",
    "ext_skin_c.png",
    "ext_skin_d.png",
    "ext_skin_m.png",
    "ext_skin_r.png",
    "ext_skin_cc.png",
    "ext_skin_cr.png",
    "ext_skin_ao.png",
    "ext_skin_drift_ao.png",
    "ext_banner_c.png",
    road_plates.PROJECT_PLATE_PNG.lower(),
    "ext_windows_c.png",
    "ext_windows_d.png",
    "ext_windows_m.png",
    "ext_windows_r.png",
    "ext_windows_cc.png",
    "ext_windows_cr.png",
    "ext_skin_uv.png",
}
LIVERY_PROJECT_MANAGED_DIRNAMES = {
    ".liverylab",
    "_cache",
    "_preview",
    "cache",
    "models",
    "preview_assets",
}

# -----------------------------------------------------------------------------
# Settings / backup helpers
# -----------------------------------------------------------------------------

APP_MANAGED_STORAGE_DIR_NAMES = {
    "liverylibrary",
    "liveryprojects",
    "liverypackageexport",
    "officialliverylibrary",
    "officialliveryprojects",
    "officialliverypackageexport",
    "preview_assets",
    "previewassets",
    "userdata",
}


def is_app_managed_storage_path(path: str | Path) -> bool:
    return any(part.lower() in APP_MANAGED_STORAGE_DIR_NAMES for part in Path(path).parts)


@dataclass
class PackPackageEntry:
    name: str
    unk_always_0: int
    flags: int
    file_name_length: int
    path_hash: int
    file_size: int
    file_offset: int

    @property
    def is_directory(self) -> bool:
        return bool(self.flags & 0x0001)

    @property
    def is_encrypted(self) -> bool:
        return bool(self.flags & 0x0100)

def get_portable_storage_root() -> Path:
    root = get_program_root()
    release_root = root / "release"
    if (release_root / "_internal").exists():
        return release_root
    return root


def get_user_data_root() -> Path:
    root = get_portable_storage_root() / USER_DATA_DIRNAME
    root.mkdir(parents=True, exist_ok=True)
    return root


def get_webview_storage_path() -> Path:
    root = get_user_data_root() / WEBVIEW_USER_DATA_DIRNAME
    root.mkdir(parents=True, exist_ok=True)
    return root


def get_package_state_manifest_root() -> Path:
    root = get_user_data_root() / "PackageState"
    root.mkdir(parents=True, exist_ok=True)
    return root


def get_advanced_content_setup_status(root: str | Path) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    package = package_state.content_package_status(game_root, get_package_state_manifest_root())
    return _json_ready(advanced_content_setup.status(get_package_state_manifest_root(), game_root, package))


def record_advanced_content_setup_success(
    root: str | Path,
    pack_path: str | Path,
    output_dir: str | Path,
    extraction_result: dict[str, Any],
) -> dict[str, Any]:
    return _json_ready(
        advanced_content_setup.record_success(
            get_package_state_manifest_root(),
            normalize_game_root(root),
            pack_path,
            output_dir,
            extraction_result,
        )
    )


def invalidate_advanced_content_setup(root: str | Path, reason: str) -> dict[str, Any]:
    return _json_ready(
        advanced_content_setup.invalidate(
            get_package_state_manifest_root(),
            normalize_game_root(root),
            reason,
        )
    )


def require_advanced_content_setup_ready(root: str | Path) -> dict[str, Any]:
    status = get_advanced_content_setup_status(root)
    if status.get("ready") is not True:
        raise RuntimeError(
            f"Advanced content setup is not complete: {status.get('reason') or 'Run Extract all in Advanced Setup first.'}"
        )
    return status


def get_package_import_temp_root() -> Path:
    root = get_portable_storage_root() / "_tmp" / "pi"
    root.mkdir(parents=True, exist_ok=True)
    return root


def get_short_work_temp_root(kind: str) -> Path:
    safe_kind = re.sub(r"[^a-z0-9_-]+", "", str(kind or "").strip().lower())[:16] or "w"
    root = get_portable_storage_root() / "_tmp" / safe_kind
    root.mkdir(parents=True, exist_ok=True)
    return root


def create_short_work_temp_dir(kind: str) -> Path:
    root = get_short_work_temp_root(kind)
    for _attempt in range(20):
        tmp_dir = root / uuid.uuid4().hex[:10]
        try:
            tmp_dir.mkdir(parents=True, exist_ok=False)
            return tmp_dir
        except FileExistsError:
            continue
    raise FileExistsError(f"Could not create unique temp folder under: {root}")


def cleanup_short_work_temp_dir(tmp_dir: Path | None) -> None:
    if tmp_dir is None:
        return
    shutil.rmtree(tmp_dir, ignore_errors=True)
    parent = tmp_dir.parent
    try:
        if parent.exists() and not any(parent.iterdir()):
            parent.rmdir()
    except OSError:
        pass


def _short_export_label(value: Any, fallback: str, max_length: int = 24) -> str:
    label = sanitize_slug(str(value or fallback))
    label = label[:max(1, int(max_length))].strip("_-")
    return label or sanitize_slug(fallback)


def _short_export_hash(*parts: Any) -> str:
    text = "\n".join(str(part or "").strip().lower() for part in parts if str(part or "").strip())
    if not text:
        text = "liverylab-package"
    return hashlib.sha1(text.encode("utf-8")).hexdigest()[:6]


def short_export_package_name(prefix: str, label: Any, *identity_parts: Any, max_label_length: int = 24) -> str:
    clean_prefix = sanitize_slug(prefix)[:12].strip("_-") or "pkg"
    short_label = _short_export_label(label, clean_prefix, max_label_length)
    digest = _short_export_hash(clean_prefix, *identity_parts, label)
    return f"{clean_prefix}_{short_label}_{digest}"


def _legacy_settings_paths() -> list[Path]:
    paths: list[Path] = []
    base = os.getenv("APPDATA")
    if base:
        paths.append(Path(base) / APP_NAME / SETTINGS_FILENAME)
    paths.append(Path.home() / f".{APP_NAME.lower()}" / SETTINGS_FILENAME)
    return paths


def _cleanup_legacy_settings_path(path: Path) -> None:
    try:
        path.unlink(missing_ok=True)
    except Exception:
        return
    parent = path.parent
    try:
        parent.rmdir()
    except Exception:
        pass


def _archive_legacy_settings_path(legacy: Path, target: Path, index: int) -> None:
    try:
        if target.exists() and legacy.read_bytes() == target.read_bytes():
            return
    except Exception:
        pass
    backup = target.with_name(f"settings.legacy-{index}.json")
    if backup.exists():
        return
    try:
        shutil.copy2(legacy, backup)
    except Exception:
        pass


def _migrate_legacy_settings(target: Path) -> None:
    for index, legacy in enumerate(_legacy_settings_paths(), start=1):
        if not legacy.exists() or legacy == target:
            continue
        try:
            target.parent.mkdir(parents=True, exist_ok=True)
            if target.exists():
                _archive_legacy_settings_path(legacy, target, index)
            else:
                shutil.copy2(legacy, target)
            _cleanup_legacy_settings_path(legacy)
        except Exception:
            continue


def get_settings_path() -> Path:
    path = get_user_data_root() / SETTINGS_FILENAME
    _migrate_legacy_settings(path)
    return path


def get_program_root() -> Path:
    root = Path(__file__).resolve().parent
    if root.name == "_internal":
        return root.parent
    return root


def load_debug_config() -> dict[str, Any]:
    enabled = any(str(arg).strip().lower() == DEBUG_TOOLS_ARG for arg in sys.argv[1:])
    return {
        "enabled": enabled,
        "source": DEBUG_TOOLS_ARG if enabled else "",
        "tools": {"wheelCalibration": enabled, "numberplateCalibration": enabled} if enabled else {},
    }


def is_debug_tool_enabled(tool_name: str) -> bool:
    config = load_debug_config()
    return bool(config.get("enabled"))


def _folder_storage_root(folder_name: str) -> Path:
    root = get_program_root()
    if folder_name in {LIVERY_PACKAGE_EXPORT_DIRNAME, LIVERY_PROJECTS_DIRNAME, LIVERY_LIBRARY_DIRNAME, USER_DATA_DIRNAME, online_league.PROJECTS_DIRNAME, online_league.LIBRARY_DIRNAME, online_league.PACKAGE_EXPORT_DIRNAME}:
        release_root = root / "release"
        if (release_root / "_internal").exists():
            return release_root
    return root


def _move_or_merge_folder(source: Path, target: Path) -> None:
    if not source.exists() or source.resolve() == target.resolve():
        return
    target.parent.mkdir(parents=True, exist_ok=True)
    if not target.exists():
        try:
            source.replace(target)
            return
        except Exception:
            pass
    if source.is_dir():
        target.mkdir(parents=True, exist_ok=True)
        for item in source.iterdir():
            destination = target / item.name
            if destination.exists():
                continue
            try:
                item.replace(destination)
            except Exception:
                if item.is_dir():
                    shutil.copytree(item, destination)
                else:
                    shutil.copy2(item, destination)
        try:
            source.rmdir()
        except Exception:
            pass
    else:
        if not target.exists():
            shutil.copy2(source, target)


LIVERY_PACKAGE_EXPORT_README = generated_readmes.LIVERY_PACKAGE_EXPORT_README
LIVERY_PROJECTS_README = generated_readmes.ADVANCED_PROJECTS_README
LIVERY_LIBRARY_README = generated_readmes.ADVANCED_LIBRARY_README
CONVERTED_README = generated_readmes.ADVANCED_CONVERTED_README


def _write_folder_readme(folder: Path, text: str) -> None:
    folder.mkdir(parents=True, exist_ok=True)
    readme = folder / "README.txt"
    if not readme.exists():
        readme.write_text(text, encoding="utf-8")
        return
    existing = readme.read_text(encoding="utf-8", errors="ignore")
    if generated_readmes.is_managed_root_readme(existing):
        readme.write_text(text, encoding="utf-8")


def _migrate_root_folder_into_program_root(game_root: Path, folder_name: str, legacy_names: list[str] | None = None) -> Path:
    program_target = _folder_storage_root(folder_name) / folder_name
    base_root = normalize_game_root(game_root)
    names = [folder_name, *(legacy_names or [])]
    for candidate_name in names:
        legacy_target = base_root / candidate_name
        if legacy_target.exists() and legacy_target != program_target:
            _move_or_merge_folder(legacy_target, program_target)
        program_legacy = _folder_storage_root(folder_name) / candidate_name
        if program_legacy.exists() and program_legacy != program_target:
            _move_or_merge_folder(program_legacy, program_target)
    return program_target


def get_custom_liveries_root(game_root: str | Path | None = None) -> Path:
    return get_livery_packages_root(game_root)


def get_livery_package_export_root(game_root: str | Path | None = None) -> Path:
    base_root = normalize_game_root(game_root or Path.cwd())
    root = _migrate_root_folder_into_program_root(base_root, LIVERY_PACKAGE_EXPORT_DIRNAME, LEGACY_LIVERY_PACKAGE_EXPORT_DIRNAMES)
    _write_folder_readme(root, LIVERY_PACKAGE_EXPORT_README)
    return root


def get_livery_packages_root(game_root: str | Path | None = None) -> Path:
    return get_livery_package_export_root(game_root)


def get_official_livery_projects_root(create: bool = True) -> Path:
    root = official_storage.projects_root(get_portable_storage_root(), create=create)
    if create:
        _write_folder_readme(root, generated_readmes.OFFICIAL_PROJECTS_README)
    return root


def get_official_livery_library_root(create: bool = True) -> Path:
    root = official_storage.library_root(get_portable_storage_root(), create=create)
    if create:
        _write_folder_readme(root, generated_readmes.OFFICIAL_LIBRARY_README)
    return root


def get_official_livery_package_export_root(create: bool = True) -> Path:
    root = official_storage.package_export_root(get_portable_storage_root(), create=create)
    if create:
        _write_folder_readme(root, generated_readmes.OFFICIAL_PACKAGE_EXPORT_README)
    return root


def get_official_preview_assets_root(create: bool = True) -> Path:
    if create:
        get_official_livery_library_root(create=True)
    return official_storage.preview_assets_root(get_portable_storage_root(), create=create)


def get_official_preview_raw_workspace_root(create: bool = True) -> Path:
    return official_preview_storage.raw_workspace_root(get_portable_storage_root(), create=create)


def get_official_saved_car_templates_root(create: bool = True) -> Path:
    if create:
        get_official_livery_library_root(create=True)
    return official_storage.saved_car_templates_root(get_portable_storage_root(), create=create)


def get_official_local_skins_root(create: bool = True) -> Path:
    if create:
        get_official_livery_library_root(create=True)
    return official_storage.local_skins_root(get_portable_storage_root(), create=create)


def get_official_trash_root(create: bool = True) -> Path:
    if create:
        get_official_livery_library_root(create=True)
    return official_storage.trash_root(get_portable_storage_root(), create=create)


def get_official_storage_roots(create: bool = False) -> dict[str, Any]:
    return official_storage.roots(get_portable_storage_root(), create=create)


ONLINE_LEAGUE_PROJECTS_README = generated_readmes.ONLINE_LEAGUE_PROJECTS_README
ONLINE_LEAGUE_LIBRARY_README = generated_readmes.ONLINE_LEAGUE_LIBRARY_README
ONLINE_LEAGUE_PACKAGE_EXPORT_README = generated_readmes.ONLINE_LEAGUE_PACKAGE_EXPORT_README


def get_online_league_projects_root(create: bool = True) -> Path:
    root = _folder_storage_root(online_league.PROJECTS_DIRNAME) / online_league.PROJECTS_DIRNAME
    if create:
        _write_folder_readme(root, ONLINE_LEAGUE_PROJECTS_README)
    return root


def get_online_league_library_root(create: bool = True) -> Path:
    root = _folder_storage_root(online_league.LIBRARY_DIRNAME) / online_league.LIBRARY_DIRNAME
    if create:
        _write_folder_readme(root, ONLINE_LEAGUE_LIBRARY_README)
    return root


def get_online_league_package_export_root(create: bool = True) -> Path:
    root = _folder_storage_root(online_league.PACKAGE_EXPORT_DIRNAME) / online_league.PACKAGE_EXPORT_DIRNAME
    if create:
        _write_folder_readme(root, ONLINE_LEAGUE_PACKAGE_EXPORT_README)
    return root


def _online_league_roots() -> online_league.OnlineLeagueRoots:
    return online_league.OnlineLeagueRoots(
        projects=get_online_league_projects_root(),
        library=get_online_league_library_root(),
        package_export=get_online_league_package_export_root(),
        temp=get_package_import_temp_root() / "online_league",
        official_projects=get_official_livery_projects_root(create=False),
    )


def get_online_league_roots(create: bool = False) -> dict[str, Any]:
    return online_league.roots_info(_online_league_roots(), create=create)


def get_official_car_resources_status(root: str | Path | None = None) -> dict[str, Any]:
    game_root = Path(root).expanduser() if root else None
    package_error = ""
    try:
        pack_path = package_state.content_package_source_path(
            game_root,
            get_package_state_manifest_root(),
        ) if game_root else None
    except RuntimeError as exc:
        pack_path = None
        package_error = str(exc)
    status = official_car_resources.inspect(
        get_official_preview_assets_root(create=False),
        pack_path=pack_path,
        raw_preview_assets_root=get_official_preview_raw_workspace_root(create=False),
    )
    if pack_path is not None and pack_path.exists() and status.get("manifestExists"):
        reuse = official_preview_pipeline.reusable_preview_assets(
            get_official_preview_assets_root(create=False),
            pack_path,
            exporter_version=preview3d.GLB_EXPORTER_VERSION,
        )
        status["current"] = bool(reuse.get("reusable"))
        status["refreshRequired"] = not bool(reuse.get("reusable"))
        status["refreshReason"] = str(reuse.get("reason") or "")
        if not reuse.get("reusable"):
            status["ready"] = False
            status["derivedReady"] = False
    status["packageError"] = package_error
    return status


def get_livery_projects_root() -> Path:
    root = _folder_storage_root(LIVERY_PROJECTS_DIRNAME) / LIVERY_PROJECTS_DIRNAME
    root.mkdir(parents=True, exist_ok=True)
    _write_folder_readme(root, LIVERY_PROJECTS_README)
    return root


def get_livery_library_root() -> Path:
    root = _folder_storage_root(LIVERY_LIBRARY_DIRNAME) / LIVERY_LIBRARY_DIRNAME
    legacy_local = get_livery_projects_root() / LOCAL_SKINS_DIRNAME
    target_local = root / LOCAL_SKINS_DIRNAME
    if legacy_local.exists() and legacy_local != target_local:
        _move_or_merge_folder(legacy_local, target_local)
    _write_folder_readme(root, LIVERY_LIBRARY_README)
    return root


def _livery_start_point_template_with_source_material(
    root: str | Path | None,
    car_id: str,
    template: dict[str, Any] | None,
) -> dict[str, Any] | None:
    if not isinstance(template, dict):
        return template
    data = dict(template)
    if any(str(data.get(key) or "").strip() for key in ("materialPath", "material", "sourceMaterialPath", "sourceMaterial")):
        return data
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    cardesign_ref = str(data.get("cardesignPath") or data.get("cardesign") or "").strip()
    design_ref = str(data.get("design") or "").strip()
    if not cardesign_ref:
        return data
    try:
        cardesign_path = _resolve_template_path(game_root, cardesign_ref)
        if not cardesign_path.exists():
            return data
        if not design_ref:
            design_from_cardesign, _rim_path = extract_cardesign_info(cardesign_path.read_bytes())
            design_ref = str(design_from_cardesign or "").strip()
            if design_ref:
                data["design"] = design_ref
        if not design_ref:
            return data
        design_path = _resolve_template_path(game_root, design_ref)
        if not design_path.exists():
            return data
        info = extract_design_info(cardesign_path.read_bytes(), design_path.read_bytes())
        source_car_id = split_content_path(str(info.car_content_path or ""))[-1] if info.car_content_path else ""
        if car_id and source_car_id and source_car_id.lower() != str(car_id).strip().lower():
            return data
        material_ref = norm_slashes(str(info.ext_skin_material_path or ""))
        if material_ref:
            data["sourceMaterial"] = material_ref
            data["material"] = material_ref
    except Exception:
        return data
    return data


def list_livery_start_points(
    car_id: str = "",
    workflow: str = "",
    root: str | Path | None = None,
    template: dict[str, Any] | None = None,
) -> dict[str, Any]:
    game_root = str(root or get_default_root_folder() or "").strip() or None
    prepared_template = _livery_start_point_template_with_source_material(game_root, car_id, template)
    return _json_ready(livery_start_points.list_start_points(
        root=game_root,
        car_id=car_id,
        workflow=workflow,
        template=prepared_template,
    ))


def inspect_livery_template_preview_assets(
    car_id: str,
    cardesign_id: str = "",
    workflow: str = "",
    root: str | Path | None = None,
) -> dict[str, Any]:
    game_root = str(root or get_default_root_folder() or "").strip() or None
    return _json_ready(livery_template_preview.inspect_template_preview_assets(
        car_id=car_id,
        cardesign_id=cardesign_id,
        workflow=workflow,
        game_root=game_root,
        official_preview_assets_root=get_official_preview_assets_root(create=False),
        livery_library_root=get_livery_library_root(),
    ))


def get_read_only_livery_preview_root() -> Path:
    root = get_livery_library_root() / "PreviewCache"
    root.mkdir(parents=True, exist_ok=True)
    return root


def get_preview_assets_root() -> Path:
    root = get_livery_library_root() / "PreviewAssets"
    root.mkdir(parents=True, exist_ok=True)
    return root


def get_advanced_creator_preview_assets_root() -> Path:
    root = get_preview_assets_root() / "AdvancedCreator"
    root.mkdir(parents=True, exist_ok=True)
    return root


def get_local_skins_root() -> Path:
    root = get_livery_library_root() / LOCAL_SKINS_DIRNAME
    root.mkdir(parents=True, exist_ok=True)
    return root


def get_tyre_brands_root() -> Path:
    root = get_livery_library_root() / TYRE_BRANDS_DIRNAME
    root.mkdir(parents=True, exist_ok=True)
    return root


def get_package_import_allowed_target_roots() -> tuple[Path, ...]:
    return (
        get_livery_library_root(),
        get_livery_projects_root(),
        get_official_livery_library_root(create=True),
        get_official_livery_projects_root(create=True),
    )


def assert_package_import_target(path: str | Path, label: str = "package import target") -> Path:
    return package_security.assert_target_within_roots(
        path,
        get_package_import_allowed_target_roots(),
        label=label,
    )


def copy_package_import_file(source: str | Path, target: str | Path, label: str = "package import target") -> None:
    safe_target = assert_package_import_target(target, label)
    package_zip.copy_file(source, safe_target)


def copy_package_import_tree(source: str | Path, target: str | Path, label: str = "package import target") -> int:
    source_path = Path(source)
    safe_target = assert_package_import_target(target, label)
    if safe_target.exists():
        shutil.rmtree(safe_target)
    safe_target.mkdir(parents=True, exist_ok=True)
    copied = 0
    for child in sorted(source_path.rglob("*"), key=lambda item: item.as_posix().lower()):
        if not child.is_file():
            continue
        copy_package_import_file(child, safe_target / child.relative_to(source_path), label)
        copied += 1
    return copied


def validate_package_import_structure(
    package_root: str | Path,
    allowed_top_level: set[str],
    label: str,
    required_files: set[str] | None = None,
) -> dict[str, Any]:
    return package_security.validate_package_root_structure(
        package_root,
        allowed_top_level=allowed_top_level,
        required_files=required_files or {"manifest.json"},
        label=label,
    )


def get_driver_profiles_root() -> Path:
    return driver_library.drivers_root(get_livery_library_root())


def get_grid_presets_root() -> Path:
    return grid_presets.presets_root(get_livery_library_root())


def get_custom_grid_gamefile_backup_root() -> Path:
    root = get_user_data_root() / GAME_FILE_BACKUPS_DIRNAME / "CustomGrids"
    root.mkdir(parents=True, exist_ok=True)
    return root


def get_multiplayer_safety_gamefile_backup_root() -> Path:
    root = get_user_data_root() / GAME_FILE_BACKUPS_DIRNAME / "LiveryLabSafety"
    root.mkdir(parents=True, exist_ok=True)
    return root


def get_my_skins_gamefile_backup_root() -> Path:
    root = get_user_data_root() / GAME_FILE_BACKUPS_DIRNAME / "MySkins"
    root.mkdir(parents=True, exist_ok=True)
    return root


def _folder_usage(path: Path) -> dict[str, Any]:
    total_bytes = 0
    file_count = 0
    dir_count = 0
    errors: list[str] = []
    if not path.exists():
        return {"bytes": 0, "fileCount": 0, "dirCount": 0, "errors": []}
    stack = [path]
    while stack:
        current = stack.pop()
        try:
            with os.scandir(current) as entries:
                for entry in entries:
                    try:
                        if entry.is_dir(follow_symlinks=False):
                            dir_count += 1
                            stack.append(Path(entry.path))
                        elif entry.is_file(follow_symlinks=False):
                            file_count += 1
                            total_bytes += entry.stat(follow_symlinks=False).st_size
                    except OSError as exc:
                        errors.append(f"{entry.path}: {exc}")
        except OSError as exc:
            errors.append(f"{current}: {exc}")
    return {"bytes": total_bytes, "fileCount": file_count, "dirCount": dir_count, "errors": errors[:20]}


def inspect_gamefile_backup_cleanup() -> dict[str, Any]:
    my_skins_root = get_my_skins_gamefile_backup_root()
    usage = _folder_usage(my_skins_root)
    return {
        "mySkins": {
            "path": str(my_skins_root),
            "exists": my_skins_root.exists(),
            **usage,
        },
    }


def clear_my_skins_gamefile_backups() -> dict[str, Any]:
    root = get_my_skins_gamefile_backup_root()
    expected_parent = (get_user_data_root() / GAME_FILE_BACKUPS_DIRNAME).resolve()
    resolved = root.resolve()
    if resolved.parent != expected_parent or resolved.name != "MySkins":
        raise RuntimeError(f"Refusing to clear unexpected backup folder: {root}")
    before = _folder_usage(root)
    shutil.rmtree(root, ignore_errors=True)
    root.mkdir(parents=True, exist_ok=True)
    after = _folder_usage(root)
    return {
        "path": str(root),
        "clearedBytes": before.get("bytes", 0),
        "clearedFiles": before.get("fileCount", 0),
        "clearedDirs": before.get("dirCount", 0),
        "before": before,
        "after": after,
    }


def get_driver_projects_root(create: bool = True) -> Path:
    return driver_projects.projects_root(get_livery_projects_root(), create=create)


def local_skin_root_for(car_content_path: str, skin_id: str) -> Path:
    car_model = split_content_path(car_content_path)[-1] if car_content_path else "car"
    return get_local_skins_root() / sanitize_slug(car_model) / sanitize_slug(skin_id or "skin")


def get_my_skins_registry_path() -> Path:
    root = get_livery_library_root()
    root.mkdir(parents=True, exist_ok=True)
    return root / MY_SKINS_REGISTRY_FILENAME


def get_converted_root() -> Path:
    root = get_livery_library_root() / CONVERTED_DIRNAME
    _write_folder_readme(root, CONVERTED_README)
    return root


def get_converted_textures_root() -> Path:
    root = get_converted_root() / CONVERTED_TEXTURES_DIRNAME
    root.mkdir(parents=True, exist_ok=True)
    return root


def get_converted_meshes_root() -> Path:
    root = get_converted_root() / CONVERTED_MESHES_DIRNAME
    root.mkdir(parents=True, exist_ok=True)
    return root


def get_converted_uv_root() -> Path:
    root = get_converted_root() / CONVERTED_UV_DIRNAME
    root.mkdir(parents=True, exist_ok=True)
    return root


def _car_id_from_source_path(source_path: str | Path | None, game_root: str | Path | None = None) -> str:
    if source_path is None:
        return CONVERTED_UNASSIGNED_DIRNAME
    candidates = [Path(str(source_path).strip())]
    if game_root:
        root_path = normalize_game_root(game_root)
        raw_path = Path(str(source_path).strip())
        if not raw_path.is_absolute():
            candidates.insert(0, root_path / raw_path)
    for candidate in candidates:
        parts = [str(part) for part in candidate.parts]
        lower_parts = [part.lower() for part in parts]
        for index in range(0, max(0, len(lower_parts) - 2)):
            if lower_parts[index] == "content" and lower_parts[index + 1] == "cars":
                car_id = sanitize_slug(parts[index + 2])
                return car_id if car_id else CONVERTED_UNASSIGNED_DIRNAME
    return CONVERTED_UNASSIGNED_DIRNAME


def get_converted_output_root(kind: str, source_path: str | Path | None = None, game_root: str | Path | None = None) -> Path:
    key = str(kind or "").strip().lower()
    if key in {"texture", "textures", "dds", "texturemips"}:
        root = get_converted_textures_root()
    elif key in {"mesh", "meshes", "obj"}:
        root = get_converted_meshes_root()
    elif key in {"uv", "uvs", "uv_inspect", "uvinspect"}:
        root = get_converted_uv_root()
    else:
        raise ValueError(f"Unknown converted output kind: {kind}")
    target = root / _car_id_from_source_path(source_path, game_root)
    target.mkdir(parents=True, exist_ok=True)
    return target


def get_local_skin_root(game_root: str | Path, car_content_path: str, skin_id: str) -> Path:
    target = local_skin_root_for(car_content_path, skin_id)
    (target / "content" / "cars").mkdir(parents=True, exist_ok=True)
    return target


def _path_snapshot(root: Path) -> set[str]:
    if not root.exists():
        return set()
    paths = {""}
    for path in root.rglob("*"):
        try:
            paths.add(norm_slashes(str(path.relative_to(root))).lower())
        except Exception:
            continue
    return paths


def create_skin_cleanup_snapshot(
    root: str | Path,
    template_cardesign: str | Path,
    template_design: str | Path,
    skin_id: str,
) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    cardesign_path = Path(template_cardesign)
    design_path = resolve_template_design_path(game_root, cardesign_path, Path(template_design))
    info = extract_design_info(cardesign_path.read_bytes(), design_path.read_bytes())
    clean_skin_id = sanitize_slug(skin_id)
    target_root = local_skin_root_for(info.car_content_path or "", clean_skin_id)
    target_existed = target_root.exists()
    car_model = split_content_path(info.car_content_path or "")[-1] if info.car_content_path else ""
    existing_registry_keys: set[str] = set()
    existing_registry_roots: set[str] = set()
    try:
        for entry in load_my_skins():
            key = str(entry.get("key") or "").strip().lower()
            if key:
                existing_registry_keys.add(key)
            local_text = str(entry.get("local_root") or "").strip()
            if local_text:
                try:
                    existing_registry_roots.add(norm_slashes(str(Path(local_text).resolve())).lower())
                except Exception:
                    existing_registry_roots.add(norm_slashes(local_text).lower())
    except Exception:
        pass
    return {
        "targetRoot": str(target_root),
        "targetExisted": target_existed,
        "existingPaths": sorted(_path_snapshot(target_root)),
        "skinKey": f"{car_model}:{clean_skin_id}" if car_model and clean_skin_id else "",
        "existingRegistryKeys": sorted(existing_registry_keys),
        "existingRegistryRoots": sorted(existing_registry_roots),
    }


def cleanup_failed_create_skin_outputs(snapshot: dict[str, Any] | None) -> dict[str, Any]:
    data = snapshot if isinstance(snapshot, dict) else {}
    target_text = str(data.get("targetRoot") or "").strip()
    if not target_text:
        return {"cleaned": False, "reason": "missing target"}
    target_root = Path(target_text)
    local_root = get_local_skins_root()
    try:
        target_root.resolve().relative_to(local_root.resolve())
    except Exception:
        return {"cleaned": False, "reason": "target outside LocalSkins", "targetRoot": str(target_root)}

    removed: list[str] = []
    errors: list[str] = []
    target_existed = bool(data.get("targetExisted"))
    existing = {str(row).lower() for row in (data.get("existingPaths") or [])}
    if target_root.exists():
        if not target_existed:
            try:
                shutil.rmtree(target_root)
                removed.append(str(target_root))
            except Exception as exc:
                errors.append(str(exc))
        else:
            for path in sorted(target_root.rglob("*"), key=lambda item: len(item.parts), reverse=True):
                try:
                    rel = norm_slashes(str(path.relative_to(target_root))).lower()
                    if rel in existing:
                        continue
                    if path.is_dir():
                        path.rmdir()
                    else:
                        path.unlink()
                    removed.append(str(path))
                except OSError as exc:
                    errors.append(str(exc))

    removed_registry = 0
    skin_key = str(data.get("skinKey") or "").strip().lower()
    existing_registry_keys = {str(row).lower() for row in (data.get("existingRegistryKeys") or [])}
    existing_registry_roots = {str(row).lower() for row in (data.get("existingRegistryRoots") or [])}
    try:
        target_resolved = target_root.resolve()
        skins = load_my_skins()
        kept: list[dict[str, Any]] = []
        for entry in skins:
            entry_key = str(entry.get("key") or "").strip().lower()
            local_text = str(entry.get("local_root") or "").strip()
            local_matches = False
            if local_text:
                try:
                    local_matches = Path(local_text).resolve() == target_resolved
                except Exception:
                    local_matches = norm_slashes(local_text).lower() == norm_slashes(str(target_root)).lower()
            try:
                local_key = norm_slashes(str(Path(local_text).resolve())).lower() if local_text else ""
            except Exception:
                local_key = norm_slashes(local_text).lower()
            if (
                (skin_key and entry_key == skin_key and entry_key not in existing_registry_keys)
                or (local_matches and local_key not in existing_registry_roots)
            ):
                removed_registry += 1
                continue
            kept.append(entry)
        if removed_registry:
            save_my_skins(kept)
    except Exception as exc:
        errors.append(f"registry cleanup failed: {exc}")

    return {
        "cleaned": bool(removed or removed_registry),
        "targetRoot": str(target_root),
        "removed": removed,
        "removedRegistryEntries": removed_registry,
        "errors": errors,
    }


def get_trash_root(game_root: str | Path | None = None) -> Path:
    base_root = normalize_game_root(game_root or Path.cwd())
    return _migrate_root_folder_into_program_root(base_root, TRASH_DIRNAME)


def load_app_settings() -> dict[str, object]:
    path = get_settings_path()
    if not path.exists():
        return {}
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        return data if isinstance(data, dict) else {}
    except Exception:
        return {}


def save_app_settings(settings: dict[str, object]) -> Path:
    path = get_settings_path()
    path.write_text(json.dumps(settings, indent=2, ensure_ascii=False), encoding="utf-8")
    return path


def load_layer_studio_color_favorites() -> dict[str, Any]:
    return {
        "favorites": layer_studio_color_favorites.load_favorites(load_app_settings()),
        "settingsPath": str(get_settings_path()),
    }


def save_layer_studio_color_favorites(values: Any) -> dict[str, Any]:
    settings, favorites = layer_studio_color_favorites.update_settings(load_app_settings(), values)
    saved = save_app_settings(settings)
    return {
        "favorites": favorites,
        "settingsPath": str(saved),
    }


def liverylab_safety_bypassed() -> bool:
    return False


def set_liverylab_safety_bypass(enabled: bool, root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    result: dict[str, Any] = {
        "enabled": bool(enabled),
        "root": str(game_root),
        "settingsPath": "",
    }
    if enabled:
        cleanup = package_refresh.cleanup_liverylab_safety_no_backup(game_root)
        result["cleanup"] = cleanup
        result["status"] = {"bypassed": True, "installed": False, "label": "Bypassed"}
    else:
        result["status"] = repair_multiplayer_safety_hook(game_root)
    return _json_ready(result)


def cleanup_local_app_storage() -> dict[str, Any]:
    return local_storage_cleanup.cleanup_app_storage(
        local_skins_root=get_local_skins_root(),
        livery_library_root=get_livery_library_root(),
        livery_projects_root=get_livery_projects_root(),
        user_data_root=get_user_data_root(),
        registry_path=get_my_skins_registry_path(),
    )


def run_startup_local_storage_cleanup() -> dict[str, Any]:
    marker = f"{APP_VERSION}:local-storage-cleanup-v1"
    settings = load_app_settings()
    if str(settings.get("local_storage_cleanup_marker") or "") == marker:
        return {"skipped": True, "reason": "already cleaned for this version", "marker": marker}
    result = cleanup_local_app_storage()
    settings["local_storage_cleanup_marker"] = marker
    settings["local_storage_cleanup_last_run"] = datetime.now().isoformat(timespec="seconds")
    settings["local_storage_cleanup_last_result"] = {
        "movedRuntimeBackupDirs": int(result.get("movedRuntimeBackupDirs") or 0),
        "movedOrphanSkinDirs": int(result.get("movedOrphanSkinDirs") or 0),
        "backupFolder": str(result.get("backupFolder") or ""),
    }
    save_app_settings(settings)
    return result


def normalize_game_root(path: str | Path) -> Path:
    candidate = Path(path).expanduser()
    try:
        candidate = candidate.resolve()
    except Exception:
        candidate = candidate.absolute()
    if candidate.name.lower() == "content" and candidate.parent.exists():
        return candidate.parent
    return candidate


def _path_is_within(child: Path, parent: Path) -> bool:
    try:
        return os.path.commonpath([str(child.resolve()), str(parent.resolve())]) == str(parent.resolve())
    except Exception:
        try:
            return os.path.commonpath([str(child.absolute()), str(parent.absolute())]) == str(parent.absolute())
        except Exception:
            return False


def _write_probe(directory: Path) -> dict[str, Any]:
    result: dict[str, Any] = {
        "path": str(directory),
        "exists": directory.exists(),
        "isDir": directory.is_dir(),
        "writable": False,
        "message": "",
    }
    if not result["exists"]:
        result["message"] = "Folder does not exist"
        return result
    if not result["isDir"]:
        result["message"] = "Path is not a folder"
        return result
    probe = directory / f".liverylab_write_test_{uuid.uuid4().hex}.tmp"
    try:
        with probe.open("x", encoding="utf-8") as handle:
            handle.write("LiveryLab write test\n")
        probe.unlink()
        result["writable"] = True
        result["message"] = "Writable"
    except PermissionError as exc:
        result["message"] = "Access denied"
        result["detail"] = str(exc)
    except OSError as exc:
        result["message"] = getattr(exc, "strerror", "") or str(exc)
        result["detail"] = str(exc)
    finally:
        try:
            if probe.exists():
                probe.unlink()
        except Exception:
            pass
    return result


def check_root_status(root: str | Path) -> dict[str, Any]:
    root_text = str(root or "").strip()
    if not root_text:
        return {
            "root": "",
            "exists": False,
            "writeReady": False,
            "underProgramFiles": False,
            "summary": "No root selected",
            "checks": [],
            "warnings": [],
        }
    root_path = normalize_game_root(root_text)
    content_path = root_path / "content"
    cars_path = content_path / "cars"
    system_path = root_path / "system"
    package_path = root_path / "content.kspkg"
    package_status = package_state.content_package_status(root_path, get_package_state_manifest_root())
    package_source_text = str(package_status.get("contentPackagePath") or "").strip()
    package_source_path = Path(package_source_text) if package_source_text else None
    package_available = bool(package_source_path and package_source_path.is_file())
    extracted_top_level = {
        name: (root_path / name).exists()
        for name in EXTRACTED_TOP_LEVEL_DIR_NAMES
    }
    checks = [
        {"key": "root", **_write_probe(root_path)},
        {"key": "content", **_write_probe(content_path)},
        {"key": "contentCars", **_write_probe(cars_path)},
        {"key": "system", **_write_probe(system_path)},
    ]
    program_dirs = [
        Path(value)
        for value in {
            os.environ.get("ProgramFiles", ""),
            os.environ.get("ProgramFiles(x86)", ""),
            os.environ.get("ProgramW6432", ""),
        }
        if value
    ]
    under_program_files = any(_path_is_within(root_path, folder) for folder in program_dirs)
    warnings: list[str] = []
    if under_program_files:
        warnings.append("AC EVO is inside Program Files. If writes fail, restart LiveryLab as administrator.")
    root_check = checks[0]
    cars_check = checks[2]
    root_writable = bool(root_check.get("writable"))
    loose_content_ready = bool(cars_path.exists() and cars_check.get("writable"))
    package_extract_ready = bool(root_path.exists() and root_writable and package_available)
    write_ready = bool(root_writable and loose_content_ready)
    if not root_path.exists():
        summary = "Root not found"
    elif not cars_path.exists() and package_extract_ready:
        summary = "content/cars not found; package extraction available"
    elif not cars_path.exists():
        summary = "content/cars not found"
    elif write_ready:
        summary = "Root writable"
    else:
        failed = next((row for row in checks if row["key"] in {"root", "contentCars"} and not row.get("writable")), None)
        summary = f"Root not writable: {failed.get('message') if failed else 'write check failed'}"
    return {
        "root": str(root_path),
        "exists": root_path.exists(),
        "contentExists": content_path.exists(),
        "contentCarsExists": cars_path.exists(),
        "contentPackageExists": package_path.is_file(),
        "contentPackageAvailable": package_available,
        "contentPackagePath": str(package_source_path or ""),
        "contentPackageRenamed": bool(package_available and not package_path.is_file()),
        "contentPackageAmbiguous": bool(package_status.get("disabledAmbiguous")),
        "contentPackageMixed": bool(package_status.get("mixedPackageState")),
        "contentPackageCandidates": list(package_status.get("disabledPaths") or []),
        "rootWritable": root_writable,
        "looseContentReady": loose_content_ready,
        "packageExtractReady": package_extract_ready,
        "packageTopLevelFolders": extracted_top_level,
        "writeReady": write_ready,
        "underProgramFiles": under_program_files,
        "summary": summary,
        "checks": checks,
        "warnings": warnings,
    }


def get_storage_health(root: str | Path | None = None) -> dict[str, Any]:
    texconv = find_texconv_executable()
    root_text = str(root or "").strip()
    long_paths = official_preview_storage.windows_long_paths_requirement(get_user_data_root())

    def folder_health(key: str, label: str, role: str, resolver) -> dict[str, Any]:
        try:
            path = resolver()
            return {"key": key, "label": label, "role": role, **_write_probe(path)}
        except Exception as exc:
            return {
                "key": key,
                "label": label,
                "role": role,
                "path": "",
                "exists": False,
                "isDir": False,
                "writable": False,
                "message": str(exc) or exc.__class__.__name__,
                "detail": traceback.format_exc(),
            }

    folders = [
        folder_health("programRoot", "App folder", "LiveryLab application files and bundled tools.", get_program_root),
        folder_health("userData", USER_DATA_DIRNAME, "Portable app settings and runtime user state.", get_user_data_root),
        folder_health("webviewData", f"{USER_DATA_DIRNAME}/{WEBVIEW_USER_DATA_DIRNAME}", "WebView runtime data kept next to the release folder.", get_webview_storage_path),
        folder_health("liveryProjects", LIVERY_PROJECTS_DIRNAME, "Editable work files and 3D preview workspace.", get_livery_projects_root),
        folder_health("liveryLibrary", LIVERY_LIBRARY_DIRNAME, "Managed local livery library used by My Liveries.", get_livery_library_root),
        folder_health("onlineLeagueProjects", online_league.PROJECTS_DIRNAME, "Local Online League organizer projects.", get_online_league_projects_root),
        folder_health("onlineLeagueLibrary", online_league.LIBRARY_DIRNAME, "Imported Online League packs kept separate from My Liveries.", get_online_league_library_root),
        folder_health("onlineLeaguePackageExport", online_league.PACKAGE_EXPORT_DIRNAME, "Distributable Online League pack exports.", get_online_league_package_export_root),
        folder_health("previewAssets", f"{LIVERY_LIBRARY_DIRNAME}/PreviewAssets", "Managed car-specific assets for official livery preview and UV tooling.", get_preview_assets_root),
        folder_health("localSkins", LOCAL_SKINS_DIRNAME, "Ready local skin files before Activate in Game.", get_local_skins_root),
        folder_health("converted", CONVERTED_DIRNAME, "Converted outputs grouped by workflow and car id.", get_converted_root),
        folder_health("convertedTextures", f"{CONVERTED_DIRNAME}/{CONVERTED_TEXTURES_DIRNAME}", "Texture converter output grouped by car id.", get_converted_textures_root),
        folder_health("convertedMeshes", f"{CONVERTED_DIRNAME}/{CONVERTED_MESHES_DIRNAME}", "Mesh converter output grouped by car id.", get_converted_meshes_root),
        folder_health("convertedUv", f"{CONVERTED_DIRNAME}/{CONVERTED_UV_DIRNAME}", "UV Inspect output grouped by car id.", get_converted_uv_root),
        folder_health(
            "liveryPackageExport",
            LIVERY_PACKAGE_EXPORT_DIRNAME,
            "Portable exported packages for backup, sharing and reinstall.",
            lambda: get_livery_package_export_root(root_text or Path.cwd()),
        ),
    ]
    try:
        game_root_status = check_root_status(root_text) if root_text else check_root_status("")
    except Exception as exc:
        game_root_status = {
            "root": root_text,
            "exists": False,
            "writeReady": False,
            "underProgramFiles": False,
            "summary": str(exc) or exc.__class__.__name__,
            "checks": [],
            "warnings": [],
        }
    return {
        "folders": folders,
        "gameRoot": game_root_status,
        "texconv": {
            "key": "texconv",
            "label": "texconv.exe",
            "path": str(texconv) if texconv else "",
            "exists": bool(texconv),
            "writable": None,
            "message": "Found" if texconv else "Not found in Settings, LiveryLab/tools or PATH",
            "role": "Required for DDS conversion and generated PNG previews.",
        },
        "longPaths": long_paths,
        "summary": {
            "appWritable": all(bool(row.get("writable")) for row in folders if row["key"] != "programRoot"),
            "gameWriteReady": bool(game_root_status.get("writeReady")),
            "texconvFound": bool(texconv),
            "longPathsEnabled": bool(long_paths.get("enabled")),
        },
    }


def get_project_capability_state(
    root: str | Path | None = None,
    skin_key: str | None = None,
    project_type: str | None = None,
) -> dict[str, Any]:
    entry: dict[str, Any] = {}
    key = str(skin_key or "").strip()
    if key:
        entry, _skins, _index = _get_my_skin_entry(key)
    resolved_type = project_type or project_types.project_type_for_entry(entry)
    resolved_root = root or entry.get("game_root") or get_default_root_folder()
    return project_capabilities.environment_state(
        resolved_root,
        project_type=resolved_type,
        car_content_path=str(entry.get("car_content_path") or ""),
        livery_library_root=get_livery_library_root(),
        official_livery_library_root=get_official_livery_library_root(create=False),
    )


def inspect_runtime_profile(
    root: str | Path | None = None,
    saved_games_ace_root: str | Path | None = None,
) -> dict[str, Any]:
    resolved_root = str(root or "").strip() or get_default_root_folder()
    return _json_ready(runtime_profiles.inspect_runtime_state(
        resolved_root,
        saved_games_ace_root=saved_games_ace_root,
        package_manifest_root=get_package_state_manifest_root(),
    ))


def preflight_runtime_switch(
    target_runtime: str,
    root: str | Path | None = None,
    saved_games_ace_root: str | Path | None = None,
) -> dict[str, Any]:
    resolved_root = str(root or "").strip() or get_default_root_folder()
    return _json_ready(runtime_switch.preflight(
        target_runtime,
        game_root=resolved_root,
        saved_games_ace_root=saved_games_ace_root,
        package_manifest_root=get_package_state_manifest_root(),
        advanced_setup_manifest_root=get_package_state_manifest_root(),
        user_data_root=get_user_data_root(),
        project_entries=load_my_skins(),
    ))


def get_runtime_profile_vault_status() -> dict[str, Any]:
    return _json_ready(runtime_switch.vault_status(get_user_data_root()))


def dry_run_runtime_switch(
    target_runtime: str,
    root: str | Path | None = None,
    saved_games_ace_root: str | Path | None = None,
) -> dict[str, Any]:
    resolved_root = str(root or "").strip() or get_default_root_folder()
    return _json_ready(runtime_switch.apply_runtime_switch(
        target_runtime,
        game_root=resolved_root,
        saved_games_ace_root=saved_games_ace_root,
        package_manifest_root=get_package_state_manifest_root(),
        advanced_setup_manifest_root=get_package_state_manifest_root(),
        user_data_root=get_user_data_root(),
        project_entries=load_my_skins(),
        dry_run=True,
        write_history=True,
    ))


def apply_runtime_switch(
    target_runtime: str,
    root: str | Path | None = None,
    saved_games_ace_root: str | Path | None = None,
    confirm_token: str = "",
) -> dict[str, Any]:
    if str(confirm_token or "").strip() != "APPLY_RUNTIME_SWITCH":
        raise PermissionError("Runtime switch apply requires explicit confirmation.")
    resolved_root = str(root or "").strip() or get_default_root_folder()
    target_scope = workflow_scopes.normalize_scope(target_runtime, default=workflow_scopes.SCOPE_ALL)
    post_package_action = None
    if target_scope == workflow_scopes.SCOPE_ADVANCED:
        post_package_action = lambda _context: ensure_advanced_runtime_safety_ready(resolved_root)
    return _json_ready(runtime_switch.apply_runtime_switch(
        target_runtime,
        game_root=resolved_root,
        saved_games_ace_root=saved_games_ace_root,
        package_manifest_root=get_package_state_manifest_root(),
        advanced_setup_manifest_root=get_package_state_manifest_root(),
        user_data_root=get_user_data_root(),
        project_entries=load_my_skins(),
        post_package_action=post_package_action,
        dry_run=False,
        write_history=True,
    ))


def discover_official_external_liveries(
    saved_games_ace_root: str | Path | None = None,
    external_liveries_root: str | Path | None = None,
    include_invalid: bool = True,
) -> dict[str, Any]:
    return official_external_liveries.discover_external_liveries(
        saved_games_ace_root=saved_games_ace_root,
        external_liveries_root=external_liveries_root,
        include_invalid=include_invalid,
    )


def inspect_official_external_livery_folder(
    folder: str | Path,
    saved_games_ace_root: str | Path | None = None,
) -> dict[str, Any]:
    return official_external_liveries.inspect_external_livery_folder(
        folder,
        saved_games_ace_root=saved_games_ace_root,
    )


def _official_external_deployment_status(source_manifest: dict[str, Any], local_manifest: dict[str, Any]) -> str:
    if not source_manifest.get("exists"):
        return "source_missing"
    if not local_manifest.get("exists"):
        return "local_missing"
    source_hash = str(source_manifest.get("sha256") or "").strip()
    local_hash = str(local_manifest.get("sha256") or "").strip()
    if source_hash and local_hash and source_hash == local_hash:
        return "matches_source"
    return "local_differs_from_source"


def _official_external_required_payload_status(
    descriptor: dict[str, Any],
    local_root: str | Path | None,
) -> str:
    source_root = Path(str(descriptor.get("externalLiveryFolder") or "")).expanduser()
    local = Path(str(local_root or "")).expanduser()
    if not source_root.is_dir():
        return "source_missing"
    if not local.is_dir():
        return "local_missing"
    sources = [source_root / official_external_liveries.EXTERNAL_LIVERY_JSON]
    texture_set = descriptor.get("textureSet") if isinstance(descriptor.get("textureSet"), dict) else {}
    for slot in texture_set.get("slots") or []:
        if not isinstance(slot, dict) or not slot.get("referenced"):
            continue
        source_text = str(slot.get("absolutePath") or "").strip()
        source = Path(source_text).expanduser() if source_text else source_root / str(slot.get("path") or "")
        sources.append(source)
    for source in sources:
        if not source.is_file():
            return "source_missing"
        try:
            relative = source.resolve().relative_to(source_root.resolve())
        except ValueError:
            return "local_differs_from_source"
        target = local / relative
        if not target.is_file() or _file_sha256(source) != _file_sha256(target):
            return "local_differs_from_source"
    return "matches_source"


def _official_external_source_id(entry: dict[str, Any]) -> str:
    return official_external_workflow.source_descriptor_id_for_library_entry(entry)


def _official_external_removed_source_id(entry: dict[str, Any]) -> str:
    source_id = _official_external_source_id(entry)
    if source_id:
        return source_id
    if project_types.project_type_for_entry(entry) != project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
        return ""
    if official_external_workflow.role_for_entry(entry) != official_external_workflow.ROLE_PROJECT:
        return ""
    details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
    target_folder = str(details.get("deploymentTargetFolder") or "").strip()
    folder_name = Path(target_folder).name if target_folder else str(
        details.get("externalFolderName")
        or details.get("plannedExternalFolder")
        or details.get("sourceFolderName")
        or ""
    ).strip()
    car_guid = official_saved_cars.normalize_guid(
        entry.get("car_guid")
        or details.get("linkedSavedCarGuid")
        or details.get("carGuid")
    )
    if not folder_name or not car_guid:
        return ""
    return f"official_external_livery:{folder_name.lower()}:{car_guid}"


def _normalize_official_external_suppressed_source_ids(value: Any) -> list[str]:
    if not isinstance(value, (list, tuple, set)):
        return []
    normalized = {
        str(item or "").strip().lower()
        for item in value
        if str(item or "").strip()
    }
    return sorted(normalized)


def _official_external_suppressed_source_ids() -> set[str]:
    settings = load_app_settings()
    return set(
        _normalize_official_external_suppressed_source_ids(
            settings.get(OFFICIAL_EXTERNAL_SUPPRESSED_SOURCE_IDS_SETTINGS_KEY)
        )
    )


def _set_official_external_source_suppressed(source_id: str, suppressed: bool) -> Path | None:
    normalized_id = str(source_id or "").strip().lower()
    if not normalized_id:
        return None
    settings = load_app_settings()
    source_ids = set(
        _normalize_official_external_suppressed_source_ids(
            settings.get(OFFICIAL_EXTERNAL_SUPPRESSED_SOURCE_IDS_SETTINGS_KEY)
        )
    )
    if suppressed:
        source_ids.add(normalized_id)
    else:
        source_ids.discard(normalized_id)
    if source_ids:
        settings[OFFICIAL_EXTERNAL_SUPPRESSED_SOURCE_IDS_SETTINGS_KEY] = sorted(source_ids)
    else:
        settings.pop(OFFICIAL_EXTERNAL_SUPPRESSED_SOURCE_IDS_SETTINGS_KEY, None)
    return save_app_settings(settings)


def _same_filesystem_path(left: str | Path | None, right: str | Path | None) -> bool:
    if not left or not right:
        return False
    try:
        return Path(left).expanduser().resolve() == Path(right).expanduser().resolve()
    except Exception:
        left_text = os.path.normcase(os.path.abspath(os.path.expanduser(str(left))))
        right_text = os.path.normcase(os.path.abspath(os.path.expanduser(str(right))))
        return left_text == right_text


def _official_external_project_deployment_owner(
    skins: list[dict[str, Any]],
    descriptor: dict[str, Any],
) -> dict[str, Any] | None:
    source_folder = Path(str(descriptor.get("externalLiveryFolder") or "")).expanduser()
    source_folder_name = str(descriptor.get("folderName") or source_folder.name or "").strip().lower()
    descriptor_guid = official_saved_cars.normalize_guid(descriptor.get("carGuid") or "")
    for item in skins:
        if not isinstance(item, dict):
            continue
        if project_types.project_type_for_entry(item) != project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
            continue
        if official_external_workflow.role_for_entry(item) != official_external_workflow.ROLE_PROJECT:
            continue
        item_guid = official_saved_cars.normalize_guid(item.get("car_guid") or "")
        if item_guid and descriptor_guid and item_guid != descriptor_guid:
            continue
        details = item.get("official_external") if isinstance(item.get("official_external"), dict) else {}
        target_folder = str(details.get("deploymentTargetFolder") or "").strip()
        if target_folder and _same_filesystem_path(target_folder, source_folder):
            return item
        project_folder_names = {
            str(details.get("externalFolderName") or "").strip().lower(),
            str(details.get("plannedExternalFolder") or "").strip().lower(),
            str(details.get("sourceFolderName") or "").strip().lower(),
        }
        if target_folder:
            project_folder_names.add(Path(target_folder).name.strip().lower())
        project_folder_names.discard("")
        if source_folder_name and source_folder_name in project_folder_names:
            return item
    return None


def _official_external_local_root_for_descriptor(descriptor: dict[str, Any]) -> Path:
    return get_official_local_skins_root(create=True) / official_external_liveries.LOCAL_LIBRARY_CAR_ID / official_external_liveries.local_library_folder_name(descriptor)


def _legacy_official_external_local_root_for_descriptor(descriptor: dict[str, Any]) -> Path:
    return get_local_skins_root() / official_external_liveries.LOCAL_LIBRARY_CAR_ID / official_external_liveries.local_library_folder_name(descriptor)


def _official_external_local_root_for_existing_entry(
    entry: dict[str, Any],
    descriptor: dict[str, Any],
) -> tuple[Path, dict[str, Any]]:
    target = _official_external_local_root_for_descriptor(descriptor)
    legacy = _legacy_official_external_local_root_for_descriptor(descriptor)
    raw_root = str(entry.get("local_root") or "").strip()
    current = Path(raw_root).expanduser() if raw_root else target
    migration: dict[str, Any] = {}
    try:
        current_resolved = current.resolve()
        target_resolved = target.resolve()
        legacy_resolved = legacy.resolve()
    except Exception:
        return current, migration
    if current_resolved == target_resolved:
        return target, migration
    if current_resolved == legacy_resolved:
        if current.exists():
            _move_or_merge_folder(current, target)
            migration = {"from": str(current), "to": str(target), "moved": True}
        elif target.exists():
            migration = {"from": str(current), "to": str(target), "moved": False, "reason": "target already exists"}
        return target, migration
    return current, migration


def sync_official_external_liveries_to_library(
    root: str | Path | None = None,
    *,
    saved_games_ace_root: str | Path | None = None,
    external_liveries_root: str | Path | None = None,
) -> dict[str, Any]:
    discovery = discover_official_external_liveries(
        saved_games_ace_root=saved_games_ace_root,
        external_liveries_root=external_liveries_root,
        include_invalid=False,
    )
    all_descriptors = [
        descriptor
        for descriptor in discovery.get("descriptors", [])
        if isinstance(descriptor, dict) and descriptor.get("valid")
    ]
    league_filter = online_league_deployments.match_league_owned_descriptors(
        get_online_league_library_root(create=False),
        all_descriptors,
    )
    league_owned_source_ids = {
        str(source_id or "").strip().lower()
        for source_id in league_filter.get("sourceIds") or []
        if str(source_id or "").strip()
    }
    suppressed_source_ids = _official_external_suppressed_source_ids()
    descriptors = [
        descriptor
        for descriptor in all_descriptors
        if str(descriptor.get("id") or "").strip().lower() not in suppressed_source_ids
        and str(descriptor.get("id") or "").strip().lower() not in league_owned_source_ids
    ]
    skins = load_my_skins()
    skins, managed_package_pruned = official_external_workflow.prune_managed_package_external_shadows(skins)
    changed = bool(managed_package_pruned)
    pruned: list[dict[str, Any]] = list(managed_package_pruned)
    existing_by_source = {
        _official_external_source_id(item): index
        for index, item in enumerate(skins)
        if isinstance(item, dict) and _official_external_source_id(item)
    }
    managed_project_deployments = {
        str(descriptor.get("id") or "").strip().lower(): _official_external_project_deployment_owner(skins, descriptor)
        for descriptor in descriptors
        if isinstance(descriptor, dict)
    }
    managed_project_deployments = {key: owner for key, owner in managed_project_deployments.items() if key and owner}
    if managed_project_deployments:
        next_skins: list[dict[str, Any]] = []
        for item in skins:
            if isinstance(item, dict) and _official_external_source_id(item) in managed_project_deployments:
                source_id = _official_external_source_id(item)
                owner = managed_project_deployments[source_id]
                pruned.append(
                    {
                        "key": item.get("key") or source_id,
                        "reason": "managed_project_deployment_shadow",
                        "projectKey": owner.get("key") or "",
                    }
                )
                changed = True
                continue
            next_skins.append(item)
        if len(next_skins) != len(skins):
            skins = next_skins
            existing_by_source = {
                _official_external_source_id(item): index
                for index, item in enumerate(skins)
                if isinstance(item, dict) and _official_external_source_id(item)
            }
    imported: list[dict[str, Any]] = []
    updated: list[dict[str, Any]] = []
    skipped: list[dict[str, Any]] = []
    skipped.extend(
        {
            "key": str(descriptor.get("id") or "").strip(),
            "reason": "removed_from_my_liveries",
            "folder": str(descriptor.get("externalLiveryFolder") or ""),
        }
        for descriptor in all_descriptors
        if str(descriptor.get("id") or "").strip().lower() in suppressed_source_ids
    )
    skipped.extend(
        {
            "key": str(descriptor.get("id") or "").strip(),
            "reason": "league_owned_deployment",
            "folder": str(descriptor.get("externalLiveryFolder") or ""),
        }
        for descriptor in all_descriptors
        if str(descriptor.get("id") or "").strip().lower() in league_owned_source_ids
    )
    warnings = [*(discovery.get("warnings") or []), *(league_filter.get("warnings") or [])]
    game_root_text = str(root or "").strip()

    for descriptor in descriptors:
        source_id = str(descriptor.get("id") or "").strip()
        source_key = source_id.lower()
        source_folder = Path(str(descriptor.get("externalLiveryFolder") or "")).expanduser()
        source_manifest = official_external_liveries.folder_manifest(source_folder)
        local_root = _official_external_local_root_for_descriptor(descriptor)
        copy_result: dict[str, Any] = {}
        project_owner = managed_project_deployments.get(source_key) or _official_external_project_deployment_owner(skins, descriptor)
        if project_owner:
            skipped.append(
                {
                    "key": source_id,
                    "reason": "managed_project_deployment",
                    "projectKey": project_owner.get("key") or "",
                    "folder": str(source_folder),
                }
            )
            continue

        if source_key in existing_by_source:
            index = existing_by_source[source_key]
            item = dict(skins[index])
            managed_package_entry = official_external_workflow.is_managed_package_library_entry(item)
            item_local_root, local_root_migration = _official_external_local_root_for_existing_entry(item, descriptor)
            if local_root_migration:
                copy_result["localRootMigration"] = local_root_migration
            if not item_local_root.exists() and source_manifest.get("exists"):
                copy_result = official_external_liveries.copy_external_livery_folder_to_library(source_folder, item_local_root)
            local_manifest = official_external_liveries.folder_manifest(item_local_root)
            status = _official_external_deployment_status(source_manifest, local_manifest)
            descriptor_car_model = official_external_liveries.car_model_from_descriptor(descriptor)
            car_model = official_external_workflow.car_model_for_library_sync(item, descriptor_car_model)
            details = dict(item.get("official_external") or {}) if isinstance(item.get("official_external"), dict) else {}
            next_details = {
                **details,
                "sourceDescriptorId": source_id,
                "sourceFolder": str(source_folder),
                "sourceJson": str(descriptor.get("externalLiveryJson") or ""),
                "sourceFolderName": str(descriptor.get("folderName") or ""),
                "savedCarFound": bool(descriptor.get("savedCarFound")),
                "savedCarPaths": list(descriptor.get("savedCarPaths") or []),
                "sourceManifest": source_manifest,
                "localManifest": local_manifest,
                "descriptor": descriptor,
            }
            if managed_package_entry:
                next_details["externalDiscoveryStatus"] = status
            else:
                next_details["deploymentStatus"] = status
                next_details["deployed"] = bool(source_manifest.get("exists"))
            if "importedAt" not in next_details:
                next_details["importedAt"] = datetime.now().isoformat(timespec="seconds")
            item["official_external"] = next_details
            item["project_type"] = project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY
            item["project_state"] = skin_policy.PROJECT_STATE_NONE
            if not managed_package_entry:
                item["origin"] = "official_external"
            item["managed_by"] = MY_SKIN_MANAGED_BY_LIVERYLAB
            item["liverylab_managed"] = True
            if not managed_package_entry:
                item["active"] = False
            item["local_root"] = str(item_local_root)
            item["car_model"] = car_model
            item["car_content_path"] = f"content\\cars\\{car_model}" if car_model and car_model != official_external_liveries.LOCAL_LIBRARY_CAR_ID else ""
            item["car_guid"] = str(descriptor.get("carGuid") or item.get("car_guid") or "")
            if game_root_text and not str(item.get("game_root") or "").strip():
                item["game_root"] = game_root_text
            item = official_external_workflow.apply_library_entry_metadata(item)
            if item != skins[index]:
                skins[index] = item
                changed = True
                updated.append({"key": item.get("key") or source_id, "status": status, "copy": copy_result})
            else:
                skipped.append({"key": item.get("key") or source_id, "reason": "already imported", "status": status})
            continue

        if not source_manifest.get("exists"):
            skipped.append({"key": source_id, "reason": "source folder missing"})
            continue
        copy_result = official_external_liveries.copy_external_livery_folder_to_library(source_folder, local_root)
        local_manifest = official_external_liveries.folder_manifest(local_root)
        entry = official_external_liveries.my_skin_entry_from_descriptor(
            descriptor,
            local_root,
            game_root=game_root_text,
            source_manifest=source_manifest,
            local_manifest=local_manifest,
        )
        details = dict(entry.get("official_external") or {})
        details["deploymentStatus"] = _official_external_deployment_status(source_manifest, local_manifest)
        entry["official_external"] = details
        skins.append(entry)
        existing_by_source[source_key] = len(skins) - 1
        imported.append({"key": entry.get("key") or source_id, "localRoot": str(local_root), "copy": copy_result})
        changed = True

    saved = save_my_skins(skins) if changed else None
    return {
        "imported": imported,
        "updated": updated,
        "skipped": skipped,
        "importedCount": len(imported),
        "updatedCount": len(updated),
        "skippedCount": len(skipped),
        "pruned": pruned,
        "prunedCount": len(pruned),
        "suppressedSourceIds": sorted(suppressed_source_ids),
        "suppressedCount": len(suppressed_source_ids),
        "leagueOwnedSourceIds": sorted(league_owned_source_ids),
        "leagueFiltered": list(league_filter.get("matches") or []),
        "leagueFilteredCount": len(league_owned_source_ids),
        "changed": changed,
        "settingsPath": str(saved) if saved else "",
        "discovery": discovery,
        "warnings": warnings,
    }


def official_external_library_view(
    root: str | Path | None = None,
    *,
    saved_games_ace_root: str | Path | None = None,
    external_liveries_root: str | Path | None = None,
) -> dict[str, Any]:
    discovery = discover_official_external_liveries(
        saved_games_ace_root=saved_games_ace_root,
        external_liveries_root=external_liveries_root,
        include_invalid=False,
    )
    descriptors = [
        descriptor
        for descriptor in discovery.get("descriptors", [])
        if isinstance(descriptor, dict) and descriptor.get("valid")
    ]
    league_filter = online_league_deployments.match_league_owned_descriptors(
        get_online_league_library_root(create=False),
        descriptors,
    )
    league_owned_source_ids = {
        str(value or "").strip().lower()
        for value in league_filter.get("sourceIds") or []
        if str(value or "").strip()
    }
    stored_items, pruned = official_external_workflow.prune_managed_package_external_shadows(load_my_skins())
    project_deployments = {
        str(descriptor.get("id") or "").strip().lower(): _official_external_project_deployment_owner(
            stored_items,
            descriptor,
        )
        for descriptor in descriptors
        if isinstance(descriptor, dict)
    }
    project_deployments = {key: value for key, value in project_deployments.items() if key and value}
    if project_deployments:
        next_items: list[dict[str, Any]] = []
        for item in stored_items:
            source_id = _official_external_source_id(item) if isinstance(item, dict) else ""
            if source_id and source_id in project_deployments:
                pruned.append({
                    "key": item.get("key") or source_id,
                    "reason": "managed_project_deployment_shadow",
                    "projectKey": project_deployments[source_id].get("key") or "",
                })
                continue
            next_items.append(item)
        stored_items = next_items

    view_items = [dict(item) for item in stored_items]
    existing_by_source = {
        _official_external_source_id(item): index
        for index, item in enumerate(view_items)
        if isinstance(item, dict) and _official_external_source_id(item)
    }
    unregistered: list[dict[str, Any]] = []
    registered: list[dict[str, Any]] = []
    skipped: list[dict[str, Any]] = []
    game_root_text = str(root or "").strip()

    for descriptor in descriptors:
        source_id = str(descriptor.get("id") or "").strip().lower()
        source_folder = Path(str(descriptor.get("externalLiveryFolder") or "")).expanduser()
        if not source_id:
            continue
        if source_id in league_owned_source_ids:
            skipped.append({"sourceDescriptorId": source_id, "reason": "league_owned_deployment"})
            continue
        if source_id in project_deployments:
            skipped.append({
                "sourceDescriptorId": source_id,
                "reason": "managed_project_deployment",
                "projectKey": project_deployments[source_id].get("key") or "",
            })
            continue

        source_manifest = official_external_liveries.folder_manifest(source_folder)
        existing_index = existing_by_source.get(source_id)
        if existing_index is not None:
            item = dict(view_items[existing_index])
            details = dict(item.get("official_external") or {}) if isinstance(item.get("official_external"), dict) else {}
            managed_package = official_external_workflow.is_managed_package_library_entry(item)
            details.update({
                "sourceDescriptorId": source_id,
                "sourceFolder": str(source_folder),
                "sourceJson": str(descriptor.get("externalLiveryJson") or ""),
                "sourceFolderName": str(descriptor.get("folderName") or source_folder.name),
                "savedCarFound": bool(descriptor.get("savedCarFound")),
                "savedCarPaths": list(descriptor.get("savedCarPaths") or []),
                "sourceManifest": source_manifest,
                "descriptor": descriptor,
                "registrationState": official_external_registration.REGISTRATION_STATE_REGISTERED,
            })
            local_manifest = official_external_liveries.folder_manifest(item.get("local_root") or "")
            details["localManifest"] = local_manifest
            external_status = _official_external_required_payload_status(descriptor, item.get("local_root"))
            if managed_package:
                details["externalDiscoveryStatus"] = external_status
            else:
                details["deploymentStatus"] = external_status
                details["deployed"] = bool(source_manifest.get("exists"))
            item["official_external"] = details
            item["registered"] = True
            item["registration_state"] = official_external_registration.REGISTRATION_STATE_REGISTERED
            item["transient"] = False
            item["active"] = bool(source_manifest.get("exists") and descriptor.get("savedCarFound"))
            item["car_guid"] = str(descriptor.get("carGuid") or item.get("car_guid") or "")
            view_items[existing_index] = item
            registered.append({"key": item.get("key") or source_id, "sourceDescriptorId": source_id})
            continue

        readiness = official_external_registration.registration_readiness(descriptor)
        entry = official_external_liveries.my_skin_entry_from_descriptor(
            descriptor,
            source_folder,
            game_root=game_root_text,
            source_manifest=source_manifest,
            local_manifest={},
        )
        details = dict(entry.get("official_external") or {})
        details.update({
            "registrationState": official_external_registration.REGISTRATION_STATE_UNREGISTERED,
            "registrationReady": bool(readiness.get("ready")),
            "registrationBlockers": list(readiness.get("blockers") or []),
            "deploymentStatus": "external_unregistered",
            "deployed": True,
        })
        entry["official_external"] = details
        entry["registered"] = False
        entry["registration_state"] = official_external_registration.REGISTRATION_STATE_UNREGISTERED
        entry["registration_ready"] = bool(readiness.get("ready"))
        entry["registration_blockers"] = list(readiness.get("blockers") or [])
        entry["transient"] = True
        entry["active"] = bool(source_manifest.get("exists") and descriptor.get("savedCarFound"))
        entry["local_root"] = str(source_folder)
        view_items.append(entry)
        existing_by_source[source_id] = len(view_items) - 1
        unregistered.append({
            "key": entry.get("key") or source_id,
            "sourceDescriptorId": source_id,
            "registrationReady": bool(readiness.get("ready")),
            "registrationBlockers": list(readiness.get("blockers") or []),
        })

    return {
        "items": view_items,
        "registered": registered,
        "registeredCount": len(registered),
        "unregistered": unregistered,
        "unregisteredCount": len(unregistered),
        "skipped": skipped,
        "skippedCount": len(skipped),
        "pruned": pruned,
        "prunedCount": len(pruned),
        "discovery": discovery,
        "warnings": [*(discovery.get("warnings") or []), *(league_filter.get("warnings") or [])],
    }


def register_official_external_livery(
    source_descriptor_id: str,
    root: str | Path | None = None,
    *,
    saved_games_ace_root: str | Path | None = None,
    external_liveries_root: str | Path | None = None,
) -> dict[str, Any]:
    if game_launcher.is_game_running():
        raise RuntimeError("Assetto Corsa EVO is running. Close the game before registering an external Official livery.")
    source_id = str(source_descriptor_id or "").strip().lower()
    if not source_id:
        raise ValueError("An Official ExternalLivery source id is required.")
    discovery = discover_official_external_liveries(
        saved_games_ace_root=saved_games_ace_root,
        external_liveries_root=external_liveries_root,
        include_invalid=False,
    )
    descriptor = next(
        (
            row
            for row in discovery.get("descriptors", [])
            if isinstance(row, dict)
            and row.get("valid")
            and str(row.get("id") or "").strip().lower() == source_id
        ),
        None,
    )
    if descriptor is None:
        raise FileNotFoundError("The detected ExternalLivery is no longer available or valid.")
    league_filter = online_league_deployments.match_league_owned_descriptors(
        get_online_league_library_root(create=False),
        [descriptor],
    )
    if league_filter.get("sourceIds"):
        raise PermissionError("Online League deployments are managed by their installed League pack.")

    skins = load_my_skins()
    existing = next(
        (
            item
            for item in skins
            if isinstance(item, dict) and _official_external_source_id(item) == source_id
        ),
        None,
    )
    if existing is not None:
        return {
            "status": "already_registered",
            "registered": False,
            "item": existing,
            "sourceDescriptorId": source_id,
            "settingsPath": "",
        }
    project_owner = _official_external_project_deployment_owner(skins, descriptor)
    if project_owner is not None:
        raise PermissionError("This ExternalLivery deployment already belongs to an editable Official project.")

    source_folder = Path(str(descriptor.get("externalLiveryFolder") or "")).expanduser()
    source_manifest = official_external_liveries.folder_manifest(source_folder)
    prototype = official_external_liveries.my_skin_entry_from_descriptor(
        descriptor,
        source_folder,
        game_root=str(root or "").strip(),
        source_manifest=source_manifest,
        local_manifest={},
    )
    validation = official_external_registration.validate_registration_source(descriptor, prototype)
    if validation.get("ready") is not True:
        errors = [str(value) for value in validation.get("errors") or [] if str(value).strip()]
        raise ValueError("Official external registration is not ready: " + "; ".join(errors[:8]))

    local_root = _official_external_local_root_for_descriptor(descriptor)
    target_preexisting = local_root.exists()
    try:
        copied = official_external_registration.copy_validated_registration_payload(validation, local_root)
        local_manifest = official_external_liveries.folder_manifest(local_root)
        entry = official_external_liveries.my_skin_entry_from_descriptor(
            descriptor,
            local_root,
            game_root=str(root or "").strip(),
            source_manifest=source_manifest,
            local_manifest=local_manifest,
        )
        saved_car = copied.get("savedCar") if isinstance(copied.get("savedCar"), dict) else {}
        generated_saved_car = official_external_registration.registered_saved_car_metadata(saved_car)
        details = dict(entry.get("official_external") or {})
        details.update({
            "registrationState": official_external_registration.REGISTRATION_STATE_REGISTERED,
            "registeredAt": copied.get("registeredAt") or datetime.now().isoformat(timespec="seconds"),
            "registrationSource": "saved_games_external_livery",
            "savedCarSource": official_saved_car_generation.SOURCE_REGISTERED_EXTERNAL,
            "generatedSavedCar": generated_saved_car,
            "linkedSavedCarGuid": generated_saved_car.get("carGuid") or entry.get("car_guid") or "",
            "deploymentStatus": "registered_active",
            "deployed": True,
        })
        entry["official_external"] = details
        entry["files"] = {
            **(entry.get("files") if isinstance(entry.get("files"), dict) else {}),
            "saved_car": generated_saved_car.get("relativePath") or "",
        }
        entry["registered"] = True
        entry["registration_state"] = official_external_registration.REGISTRATION_STATE_REGISTERED
        entry["transient"] = False
        entry["active"] = True
        entry = official_external_workflow.apply_library_entry_metadata(entry)
        skins.append(entry)
        settings_path = save_my_skins(skins)
    except Exception:
        if not target_preexisting:
            shutil.rmtree(local_root, ignore_errors=True)
        raise
    return {
        "status": "registered",
        "registered": True,
        "item": entry,
        "sourceDescriptorId": source_id,
        "localRoot": str(local_root),
        "settingsPath": str(settings_path),
        "validation": validation,
        "copy": copied,
    }


def create_official_external_project_from_library(
    skin_key: str,
    root: str | Path | None = None,
    *,
    project_name: str = "",
    overwrite: bool = False,
) -> dict[str, Any]:
    source_entry, skins, _source_index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(source_entry, "canCreateEditableProject")
    result = official_external_projects.create_project_from_library_entry(
        source_entry,
        get_official_livery_projects_root(create=True),
        project_name=project_name,
        game_root=root or source_entry.get("game_root") or get_default_root_folder(),
        preview_assets_root=get_official_preview_assets_root(create=True),
        overwrite=overwrite,
    )
    entry = result.get("entry") if isinstance(result.get("entry"), dict) else {}
    if not entry:
        raise RuntimeError("Official external project entry was not created")
    for item in skins:
        if str(item.get("key") or "").strip().lower() == str(entry.get("key") or "").strip().lower():
            raise ValueError(f"Official external project already exists in My Liveries: {entry.get('display_name') or entry.get('skin_id')}")
    skins.append(entry)
    saved = save_my_skins(skins)
    result["registryPath"] = str(saved)
    return _json_ready(result)


def _official_saved_car_reserved_guids(skins: list[dict[str, Any]]) -> list[str]:
    reserved: set[str] = set()

    def add(value: Any) -> None:
        guid = official_saved_car_state.normalize_guid(value)
        if guid:
            reserved.add(guid)

    for entry in skins:
        if not isinstance(entry, dict):
            continue
        details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
        generated = details.get("generatedSavedCar") if isinstance(details.get("generatedSavedCar"), dict) else {}
        for value in (
            entry.get("car_guid"),
            details.get("carGuid"),
            details.get("linkedSavedCarGuid"),
            generated.get("carGuid"),
        ):
            add(value)
    try:
        scan = official_saved_cars.scan_saved_cars(project_entries=skins, custom_only=False)
        for row in scan.get("items") or []:
            if not isinstance(row, dict):
                continue
            for value in (row.get("carGuid"), row.get("guid"), row.get("filenameGuid"), *(row.get("savedCarGuids") or [])):
                add(value)
    except Exception:
        pass
    try:
        discovery = official_external_liveries.discover_external_liveries()
        for descriptor in discovery.get("descriptors") or []:
            if isinstance(descriptor, dict):
                add(descriptor.get("carGuid"))
    except Exception:
        pass
    return sorted(reserved)


def _official_creator_preflight(
    *,
    livery_name: str,
    car_model: str,
    requested_external_folder_name: str = "",
    planned_external_folder_name: str = "",
    planned_skin_id: str = "",
    car_guid: str = "",
    root: str | Path | None = None,
    vehicle_variant: dict[str, Any] | None = None,
) -> dict[str, Any]:
    clean_name = str(livery_name or "").strip()
    clean_car = official_car_resources.safe_car_id(car_model, "")
    clean_guid = official_saved_car_state.normalize_guid(car_guid)
    warnings: list[str] = []
    blockers: list[str] = []
    checks: list[dict[str, Any]] = []

    if not clean_name:
        blockers.append("Livery name is required.")
    if not clean_car:
        blockers.append("Target car is required.")
    if requested_external_folder_name and planned_external_folder_name and requested_external_folder_name != planned_external_folder_name:
        warnings.append(f"ExternalLiveries folder name was adjusted to avoid conflicts: {planned_external_folder_name}.")

    resource_status = get_official_car_resources_status(root)
    creator_cars = resource_status.get("creatorCars") if isinstance(resource_status.get("creatorCars"), list) else []
    car_row = next((
        row for row in creator_cars
        if isinstance(row, dict) and official_car_resources.safe_car_id(row.get("carId") or row.get("id") or "", "") == clean_car
    ), {})
    resources_ready = bool(resource_status.get("ready"))
    car_ready = bool(car_row.get("ready")) if isinstance(car_row, dict) and car_row else False
    if not resources_ready:
        blockers.append("Official Preview Assets are not prepared. Run the official setup/resource preparation before creating a new official project.")
    if clean_car and not car_row:
        blockers.append(f"Target car is not present in the prepared official creator catalog: {clean_car}.")
    elif clean_car and not car_ready:
        blockers.append(f"Prepared official creator assets for {clean_car} are incomplete.")
    checks.append({
        "key": "official_car_resources",
        "label": "Official car resources",
        "status": "ready" if resources_ready and (not clean_car or car_ready) else "warning",
        "message": "Prepared official creator assets found." if resources_ready and (not clean_car or car_ready) else "Official creator assets need attention.",
        "carModel": clean_car,
        "resourceReady": resources_ready,
        "carReady": car_ready,
        "previewAssetsRoot": resource_status.get("previewAssetsRoot") or "",
    })

    variant = vehicle_variant if isinstance(vehicle_variant, dict) else {}
    saved_car_source = official_saved_car_generation.resolve_variant_final_state(
        get_official_preview_assets_root(create=False),
        clean_car,
        variant,
    ) if clean_car else {"ready": False, "errors": ["Target car is missing."]}
    if not saved_car_source.get("ready"):
        blockers.extend(str(row) for row in (saved_car_source.get("errors") or []) if str(row).strip())
    compatibility = official_variant_compatibility.compatibility(
        saved_car_source.get("inspection") if isinstance(saved_car_source.get("inspection"), dict) else {},
        variant,
        preview_assets_root=get_official_preview_assets_root(create=False),
    )
    if saved_car_source.get("ready") and not compatibility.get("compatible"):
        blockers.append(str(compatibility.get("reason") or "The selected variant is not compatible with Official External Liveries."))
    checks.append({
        "key": "saved_car_generation",
        "label": "Automatic SavedCar",
        "status": "ready" if saved_car_source.get("ready") else "warning",
        "message": "Variant car final state is ready for automatic SavedCar generation."
        if saved_car_source.get("ready")
        else "Variant car final state is not ready for SavedCar generation.",
        "source": saved_car_source,
    })
    checks.append({
        "key": "official_external_variant",
        "label": "Official livery variant",
        "status": "ready" if compatibility.get("compatible") else "warning",
        "message": "AC EVO marks this visual preset as available for External Liveries."
        if compatibility.get("compatible")
        else str(compatibility.get("reason") or "Variant compatibility could not be confirmed."),
        "compatibility": compatibility,
    })

    if variant and not str(variant.get("id") or variant.get("key") or "").strip():
        warnings.append("Selected vehicle variant metadata has no stable id/key.")

    return {
        "ready": not blockers,
        "blockers": blockers,
        "warnings": warnings,
        "checks": checks,
        "carModel": clean_car,
        "plannedSkinId": planned_skin_id,
        "plannedExternalFolderName": planned_external_folder_name,
        "linkedCarGuid": clean_guid,
        "savedCarMode": official_saved_car_generation.SOURCE_LIVERYLAB_GENERATED,
        "savedCarGeneration": saved_car_source,
        "variantCompatibility": compatibility,
    }


def create_new_official_external_project(
    root: str | Path | None = None,
    *,
    livery_name: str,
    car_model: str,
    external_folder_name: str = "",
    car_guid: str = "",
    author: str = "",
    description: str = "",
    thumbnail: str | Path | None = None,
    vehicle_variant: dict[str, Any] | None = None,
    saved_car_source: str | Path | None = None,
    overwrite: bool = False,
) -> dict[str, Any]:
    display_name = livery_identity.normalize_display_name(livery_name, "Official Livery")
    clean_car = official_external_projects.planned_external_folder_name(car_model, car_model).lower()
    skins = load_my_skins()
    existing_skin_ids = []
    existing_external_names = []
    for item in skins:
        if not isinstance(item, dict):
            continue
        if project_types.project_type_for_entry(item) != project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
            continue
        item_car = official_external_projects.planned_external_folder_name(
            item.get("car_model") or item.get("carModel"),
            item.get("car_model") or item.get("carModel"),
        ).lower()
        if item_car == clean_car:
            existing_skin_ids.append(item.get("skin_id") or item.get("skinId") or "")
        details = item.get("official_external") if isinstance(item.get("official_external"), dict) else {}
        existing_external_names.extend(
            [
                details.get("externalFolderName") or "",
                details.get("plannedExternalFolder") or "",
                details.get("sourceFolderName") or "",
            ]
        )
    planned_skin_id = livery_identity.unique_slug(
        livery_identity.technical_skin_id(display_name, "official_livery"),
        existing_skin_ids,
        max_length=livery_identity.TECHNICAL_ID_MAX_LENGTH,
    )
    planned_folder = livery_identity.unique_external_folder_name(
        external_folder_name or livery_identity.official_external_folder_name(car_model, planned_skin_id),
        existing_external_names,
    )
    planned_key = f"official_external_project:{clean_car}:{planned_skin_id}".lower()
    existing_project_keys = {str(item.get("key") or "").strip().lower() for item in skins if isinstance(item, dict)}
    if planned_key in existing_project_keys and not overwrite:
        raise ValueError(f"Official external project already exists: {planned_skin_id}")
    planned_folder_key = planned_folder.lower()
    for item in skins:
        if not isinstance(item, dict):
            continue
        if project_types.project_type_for_entry(item) != project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
            continue
        details = item.get("official_external") if isinstance(item.get("official_external"), dict) else {}
        existing_names = {
            str(details.get("externalFolderName") or "").strip().lower(),
            str(details.get("plannedExternalFolder") or "").strip().lower(),
            str(details.get("sourceFolderName") or "").strip().lower(),
        }
        if planned_folder_key in existing_names and not overwrite:
            raise ValueError(f"Official ExternalLiveries folder name already exists in My Liveries: {planned_folder}")
    if saved_car_source:
        source_path = Path(saved_car_source).expanduser()
        source_inspection = official_saved_car_state.inspect_saved_car_file(source_path) if source_path.is_file() else {}
        source_model = str(source_inspection.get("carModel") or "").strip()
        creator_preflight = {
            "ready": bool(source_path.is_file() and source_inspection.get("valid") and source_inspection.get("wrapped") and source_model.lower() == str(car_model or "").strip().lower()),
            "blockers": [],
            "warnings": list(source_inspection.get("warnings") or []),
            "savedCarMode": "project_duplicate",
            "savedCarSource": str(source_path),
        }
        if not source_path.is_file():
            creator_preflight["blockers"].append(f"Prepared project SavedCar is missing: {source_path}")
        elif not source_inspection.get("valid") or not source_inspection.get("wrapped"):
            creator_preflight["blockers"].append("Prepared project SavedCar is invalid.")
        elif source_model.lower() != str(car_model or "").strip().lower():
            creator_preflight["blockers"].append(f"Prepared project SavedCar targets {source_model or '-'}, expected {car_model}.")
    else:
        creator_preflight = _official_creator_preflight(
            livery_name=display_name,
            car_model=car_model,
            requested_external_folder_name=external_folder_name,
            planned_external_folder_name=planned_folder,
            planned_skin_id=planned_skin_id,
            car_guid=car_guid,
            root=root or get_default_root_folder(),
            vehicle_variant=vehicle_variant,
        )
    if not creator_preflight.get("ready"):
        blockers = creator_preflight.get("blockers") or []
        detail = "; ".join(str(blocker) for blocker in blockers if str(blocker).strip())
        raise RuntimeError(f"Official project creation is not ready: {detail or 'Creator preflight failed.'}")
    result = official_external_projects.create_new_project(
        get_official_livery_projects_root(create=True),
        livery_name=display_name,
        car_model=car_model,
        external_folder_name=planned_folder,
        skin_id=planned_skin_id,
        car_guid=car_guid,
        game_root=root or get_default_root_folder(),
        author=author,
        description=description,
        thumbnail=thumbnail,
        vehicle_variant=vehicle_variant,
        preview_assets_root=get_official_preview_assets_root(create=True),
        auto_generate_saved_car=not bool(saved_car_source),
        saved_car_source=saved_car_source,
        reserved_saved_car_guids=_official_saved_car_reserved_guids(skins),
        overwrite=overwrite,
    )
    entry = result.get("entry") if isinstance(result.get("entry"), dict) else {}
    if not entry:
        raise RuntimeError("Official external project entry was not created")
    clean_guid = official_saved_cars.normalize_guid(entry.get("car_guid") or result.get("savedCar", {}).get("carGuid") or car_guid)
    if clean_guid:
        saved_scan = list_official_saved_cars(custom_only=False)
        saved_matches = [
            row for row in saved_scan.get("items", [])
            if official_saved_cars.normalize_guid(row.get("carGuid") or row.get("guid")) == clean_guid
        ]
        details = dict(entry.get("official_external") or {}) if isinstance(entry.get("official_external"), dict) else {}
        details["linkedSavedCarGuid"] = clean_guid
        details["savedCarFound"] = bool(saved_matches)
        details["savedCarPaths"] = [str(row.get("path") or "") for row in saved_matches if str(row.get("path") or "")]
        details["linkedSavedCar"] = saved_matches[0] if saved_matches else {}
        material_batch = _official_external_material_batch_from_saved_car(details["linkedSavedCar"])
        details["materialBatch"] = material_batch
        details["linkedSavedCarMaterialBatch"] = material_batch
        entry["official_external"] = details
        entry["car_guid"] = clean_guid
        result["entry"] = entry
        official_external_project_assets.refresh_external_livery_json(
            Path(str(entry.get("local_root") or result.get("projectFolder") or "")),
            car_guid=clean_guid,
            material_batch=material_batch,
        )
    entry_key = str(entry.get("key") or "").strip().lower()
    for item in skins:
        if str(item.get("key") or "").strip().lower() == entry_key and not overwrite:
            raise ValueError(f"Official external project already exists in My Liveries: {entry.get('display_name') or entry.get('skin_id')}")
    replaced = False
    for index, item in enumerate(skins):
        if str(item.get("key") or "").strip().lower() == entry_key:
            skins[index] = entry
            replaced = True
            break
    if not replaced:
        skins.append(entry)
    saved = save_my_skins(skins)
    result["registryPath"] = str(saved)
    result["creatorPreflight"] = creator_preflight
    return _json_ready(result)


def _remove_my_skin_registry_entry(skin_key: str) -> None:
    clean_key = str(skin_key or "").strip().lower()
    if not clean_key:
        return
    skins = [
        item for item in load_my_skins()
        if str(item.get("key") or "").strip().lower() != clean_key
    ]
    save_my_skins(skins)


def _duplicate_project_category(source_key: str, target_key: str, scope: str) -> str:
    state = load_livery_categories(scope)
    assignments = state.get("assignments") if isinstance(state.get("assignments"), dict) else {}
    category_id = str(assignments.get(source_key) or "").strip()
    if category_id:
        assign_livery_category(target_key, category_id, scope)
    return category_id


def _retarget_duplicate_metadata(value: Any, source_entry: dict[str, Any], target_entry: dict[str, Any]) -> Any:
    if isinstance(value, dict):
        return {key: _retarget_duplicate_metadata(item, source_entry, target_entry) for key, item in value.items()}
    if isinstance(value, list):
        return [_retarget_duplicate_metadata(item, source_entry, target_entry) for item in value]
    if not isinstance(value, str):
        return value
    result = value
    replacements = (
        (str(_livery_project_folder_for_entry(source_entry)), str(_livery_project_folder_for_entry(target_entry))),
        (str(source_entry.get("local_root") or ""), str(target_entry.get("local_root") or "")),
    )
    for old, new in replacements:
        if old and new:
            result = result.replace(old, new)
    old_skin = str(source_entry.get("skin_id") or "").strip()
    new_skin = str(target_entry.get("skin_id") or "").strip()
    if old_skin and new_skin:
        for prefix, suffix in (("\\skins\\", "\\"), ("/skins/", "/"), ("\\presets\\", "."), ("/presets/", ".")):
            result = result.replace(f"{prefix}{old_skin}{suffix}", f"{prefix}{new_skin}{suffix}")
    return result


def _copy_duplicate_entry_authoring_state(source_entry: dict[str, Any], target_entry: dict[str, Any]) -> dict[str, Any]:
    copied: list[str] = []
    for key in (
        "metadata",
        "material_finish",
        "tyre_brand",
        "driver_profile",
        "numberplate_variant",
        "numberplate_bake",
        "road_plate",
        "start_number",
    ):
        if key not in source_entry:
            continue
        target_entry[key] = _json_ready(_retarget_duplicate_metadata(copy.deepcopy(source_entry[key]), source_entry, target_entry))
        copied.append(key)
    target_entry["active"] = False
    for key in (
        "game_sync",
        "game_sync_changes",
        "game_sync_required",
        "needs_game_update",
        "activated_at",
        "deactivated_at",
        "deployment",
        "runtime",
    ):
        target_entry.pop(key, None)
    return {"copied": copied, "copiedCount": len(copied)}


def _copy_advanced_project_authoring_assets(source_folder: Path, target_folder: Path) -> dict[str, Any]:
    if not source_folder.is_dir():
        return {"copied": [], "copiedCount": 0, "sourceMissing": True}
    target_folder.mkdir(parents=True, exist_ok=True)
    copied: list[str] = []
    allowed_directories = {"layer-studio", "project-library", ".liverylab"}
    for source in sorted((path for path in source_folder.rglob("*") if path.is_file()), key=lambda path: path.relative_to(source_folder).as_posix().lower()):
        rel = source.relative_to(source_folder)
        top = rel.parts[0].lower() if rel.parts else ""
        name = source.name.lower()
        allowed = (
            top in allowed_directories
            or source.suffix.lower() in LIVERY_PROJECT_WORK_EXTENSIONS
            or (len(rel.parts) == 1 and name in LIVERY_PROJECT_MANAGED_FILENAMES and name not in {"preview_project.json", LIVERY_PROJECT_README_FILENAME.lower()})
        )
        if not allowed:
            continue
        destination = target_folder / rel
        destination.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(source, destination)
        copied.append(rel.as_posix())
    return {
        "sourceFolder": str(source_folder),
        "projectFolder": str(target_folder),
        "copied": copied,
        "copiedCount": len(copied),
    }


def _duplicate_official_livery_project(source_entry: dict[str, Any], display_name: str) -> dict[str, Any]:
    source_folder = _livery_project_folder_for_entry(source_entry)
    if not source_folder.is_dir():
        raise FileNotFoundError(f"Official project folder not found: {source_folder}")
    details = source_entry.get("official_external") if isinstance(source_entry.get("official_external"), dict) else {}
    metadata = source_entry.get("metadata") if isinstance(source_entry.get("metadata"), dict) else {}
    variant = details.get("vehicleVariant") if isinstance(details.get("vehicleVariant"), dict) else {}
    thumbnail = source_folder / SKIN_THUMBNAIL_FILENAME
    generated = details.get("generatedSavedCar") if isinstance(details.get("generatedSavedCar"), dict) else {}
    files = source_entry.get("files") if isinstance(source_entry.get("files"), dict) else {}
    saved_car_relative = str(generated.get("relativePath") or files.get("saved_car") or "").strip()
    if not saved_car_relative:
        raise FileNotFoundError("This Official project has no prepared local SavedCar to duplicate.")
    saved_car_source = source_folder.joinpath(*[part for part in saved_car_relative.replace("\\", "/").split("/") if part])
    try:
        saved_car_source.resolve().relative_to(source_folder.resolve())
    except ValueError as exc:
        raise ValueError("Prepared project SavedCar path leaves the Official project folder.") from exc
    if not saved_car_source.is_file():
        raise FileNotFoundError(f"Prepared project SavedCar is missing: {saved_car_source}")
    result: dict[str, Any] | None = None
    target_folder: Path | None = None
    target_key = ""
    try:
        result = create_new_official_external_project(
            source_entry.get("game_root") or get_default_root_folder(),
            livery_name=display_name,
            car_model=str(source_entry.get("car_model") or ""),
            author=str(metadata.get("author") or ""),
            description=str(metadata.get("description") or ""),
            thumbnail=thumbnail if thumbnail.is_file() else None,
            vehicle_variant=variant,
            saved_car_source=saved_car_source,
            overwrite=False,
        )
        target_entry = result.get("entry") if isinstance(result.get("entry"), dict) else {}
        if not target_entry:
            raise RuntimeError("Official duplicate did not create a My Liveries entry")
        target_key = str(target_entry.get("key") or "")
        target_folder = Path(str(target_entry.get("local_root") or result.get("projectFolder") or ""))
        new_guid = official_saved_cars.normalize_guid(target_entry.get("car_guid"))
        if not target_folder.is_dir() or not new_guid:
            raise RuntimeError("Official duplicate did not create a valid project folder and car_guid")
        asset_copy = official_external_projects.copy_project_authoring_assets(source_folder, target_folder)
        external_config = official_external_projects.copy_external_livery_configuration(source_folder, target_folder, new_guid)
        state_copy = _copy_duplicate_entry_authoring_state(source_entry, target_entry)
        target_details = target_entry.get("official_external") if isinstance(target_entry.get("official_external"), dict) else {}
        target_details["duplicatedFromKey"] = str(source_entry.get("key") or "")
        target_details["duplicatedFromCarGuid"] = str(source_entry.get("car_guid") or "")
        target_entry["official_external"] = target_details
        _save_my_skin_entry(target_entry)
        preview = prepare_livery_preview_project(target_key, source_entry.get("game_root") or None)
        category_id = _duplicate_project_category(str(source_entry.get("key") or ""), target_key, workflow_scopes.SCOPE_OFFICIAL)
        return _json_ready({
            **result,
            "entry": target_entry,
            "sourceEntry": {"key": source_entry.get("key"), "displayName": source_entry.get("display_name")},
            "assetCopy": asset_copy,
            "externalConfiguration": external_config,
            "authoringState": state_copy,
            "preview": preview,
            "categoryId": category_id,
            "workflowScope": workflow_scopes.SCOPE_OFFICIAL,
        })
    except Exception:
        if target_key:
            _remove_my_skin_registry_entry(target_key)
        if target_folder and target_folder.is_dir() and _path_is_within(target_folder, get_official_livery_projects_root(create=False)):
            shutil.rmtree(target_folder, ignore_errors=True)
        raise


def _duplicate_advanced_livery_project(source_entry: dict[str, Any], display_name: str) -> dict[str, Any]:
    game_root = normalize_game_root(source_entry.get("game_root") or get_default_root_folder())
    files = source_entry.get("files") if isinstance(source_entry.get("files"), dict) else {}
    cardesign = _skin_file_disk_path(game_root, source_entry, files.get("cardesign"))
    design = _skin_file_disk_path(game_root, source_entry, files.get("design"))
    if not cardesign or not cardesign.is_file() or not design or not design.is_file():
        raise FileNotFoundError("Advanced project duplicate needs the local .cardesign and .design source files")
    template_info = extract_design_info(cardesign.read_bytes(), design.read_bytes())
    requested_skin_id = livery_identity.technical_skin_id(display_name, "skin_copy")
    if _skin_id_conflict_details(game_root, template_info, requested_skin_id, create_visual_preset=bool(files.get("visual_preset"))).get("conflict"):
        requested_skin_id = _suggest_available_skin_id(game_root, template_info, requested_skin_id, create_visual_preset=bool(files.get("visual_preset")))
    actor = _skin_file_disk_path(game_root, source_entry, files.get("actor"))
    visual = _skin_file_disk_path(game_root, source_entry, files.get("visual_preset"))
    mechanical = _skin_file_disk_path(game_root, source_entry, files.get("mechanical_preset"))
    carfinalstate = _skin_file_disk_path(game_root, source_entry, files.get("carfinalstate"))
    metadata = source_entry.get("metadata") if isinstance(source_entry.get("metadata"), dict) else {}
    cleanup_snapshot = create_skin_cleanup_snapshot(game_root, cardesign, design, requested_skin_id)
    target_key = ""
    target_project: Path | None = None
    try:
        created = create_skin(
            game_root,
            cardesign,
            design,
            requested_skin_id,
            display_name,
            overwrite=False,
            copy_source_skin=True,
            patch_actor=True,
            actor_file=actor if actor and actor.is_file() else None,
            create_visual_preset=bool(visual and visual.is_file()),
            visual_template=visual if visual and visual.is_file() else None,
            patch_actor_visual=True,
            update_mechanical_preset=bool(visual and visual.is_file() and mechanical and mechanical.is_file()),
            mechanical_template=mechanical if mechanical and mechanical.is_file() else None,
            patch_actor_mechanical=True,
            create_carfinalstate=bool(visual and visual.is_file() and carfinalstate and carfinalstate.is_file()),
            carfinalstate_template=carfinalstate if carfinalstate and carfinalstate.is_file() else None,
            author=str(metadata.get("author") or ""),
            description=str(metadata.get("description") or ""),
            # An existing local project is not registered in the stock actor/template
            # catalog. Its explicit preset files are validated by the normal creation
            # steps below instead of treating that expected absence as unsupported.
            allow_unregistered_project_template=True,
        )
        target_entry = created.my_skin_entry if isinstance(created.my_skin_entry, dict) else {}
        if not target_entry:
            raise RuntimeError("Advanced duplicate did not create a My Liveries entry")
        target_key = str(target_entry.get("key") or "")
        source_project = _livery_project_folder_for_entry(source_entry)
        prepare_livery_preview_project(target_key, game_root)
        target_project = _livery_project_folder_for_entry(target_entry)
        asset_copy = _copy_advanced_project_authoring_assets(source_project, target_project)
        state_copy = _copy_duplicate_entry_authoring_state(source_entry, target_entry)
        target_entry["duplicated_from_key"] = str(source_entry.get("key") or "")
        _save_my_skin_entry(target_entry)
        preview = prepare_livery_preview_project(target_key, game_root)
        category_id = _duplicate_project_category(str(source_entry.get("key") or ""), target_key, workflow_scopes.SCOPE_ADVANCED)
        return _json_ready({
            "entry": target_entry,
            "projectFolder": str(target_project),
            "localRoot": str(target_entry.get("local_root") or ""),
            "sourceEntry": {"key": source_entry.get("key"), "displayName": source_entry.get("display_name")},
            "assetCopy": asset_copy,
            "authoringState": state_copy,
            "preview": preview,
            "categoryId": category_id,
            "workflowScope": workflow_scopes.SCOPE_ADVANCED,
            "warnings": list(created.warnings or []),
        })
    except Exception:
        if target_key:
            _remove_my_skin_registry_entry(target_key)
        cleanup_failed_create_skin_outputs(cleanup_snapshot)
        if target_project and target_project.is_dir() and _path_is_within(target_project, get_livery_projects_root()):
            shutil.rmtree(target_project, ignore_errors=True)
        raise


def duplicate_livery_project(skin_key: str, display_name: str) -> dict[str, Any]:
    source_entry, _skins, _index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(source_entry, "canDuplicateProject")
    clean_name = livery_identity.normalize_display_name(display_name, "")
    if not clean_name:
        raise ValueError("A name is required for the duplicated livery")
    if project_types.project_type_for_entry(source_entry) == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
        return _duplicate_official_livery_project(source_entry, clean_name)
    return _duplicate_advanced_livery_project(source_entry, clean_name)


def _official_external_project_entry(skin_key: str) -> tuple[dict[str, Any], Path]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    if project_types.project_type_for_entry(entry) != project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
        raise ValueError("This livery is not an official external livery project.")
    if official_external_workflow.role_for_entry(entry) != official_external_workflow.ROLE_PROJECT:
        raise ValueError("Only editable official external projects support this action.")
    return entry, _livery_project_folder_for_entry(entry)


def _official_external_deployable_folder(entry: dict[str, Any]) -> Path | None:
    folder = _entry_local_root(entry)
    if not folder or not folder.exists() or not folder.is_dir():
        return None
    if (folder / official_external_liveries.EXTERNAL_LIVERY_JSON).is_file():
        return folder
    details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
    folder_names = [
        str(details.get("externalFolderName") or "").strip(),
        str(details.get("sourceFolderName") or "").strip(),
        str(entry.get("skin_id") or "").strip(),
    ]
    content_root = folder / "content" / "official_external_liveries"
    for folder_name in folder_names:
        candidate = content_root / folder_name
        if folder_name and (candidate / official_external_liveries.EXTERNAL_LIVERY_JSON).is_file():
            return candidate
    matches = [path.parent for path in content_root.glob(f"*/{official_external_liveries.EXTERNAL_LIVERY_JSON}") if path.is_file()]
    return matches[0] if len(matches) == 1 else None


def _official_external_deployment_entry(skin_key: str) -> tuple[dict[str, Any], Path]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    if project_types.project_type_for_entry(entry) != project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
        raise ValueError("This livery is not an official external livery.")
    role = official_external_workflow.role_for_entry(entry)
    if role not in {official_external_workflow.ROLE_PROJECT, official_external_workflow.ROLE_LIBRARY}:
        raise ValueError("This Official external livery cannot be deployed from its current storage role.")
    folder = _official_external_deployable_folder(entry)
    if not folder:
        raise FileNotFoundError("Official external livery folder is missing.")
    return entry, folder


def _official_external_project_car_guid(
    entry: dict[str, Any],
    project_folder: Path,
    override: str | None = None,
) -> str:
    def clean(value: Any) -> str:
        return str(value or "").strip()

    candidates: list[Any] = []
    if override is not None:
        candidates.append(override)
    details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
    descriptor = details.get("descriptor") if isinstance(details.get("descriptor"), dict) else {}
    candidates.extend([
        entry.get("car_guid"),
        details.get("carGuid"),
        details.get("linkedSavedCarGuid"),
        descriptor.get("carGuid"),
    ])

    manifest_path = project_folder / official_external_projects.PROJECT_MANIFEST
    if manifest_path.exists() and manifest_path.is_file():
        try:
            manifest_data = json.loads(manifest_path.read_text(encoding="utf-8"))
            manifest = manifest_data if isinstance(manifest_data, dict) else {}
        except Exception:
            manifest = {}
        project = manifest.get("project") if isinstance(manifest.get("project"), dict) else {}
        asset_status = manifest.get("assetStatus") if isinstance(manifest.get("assetStatus"), dict) else {}
        preflight = manifest.get("deploymentPreflight") if isinstance(manifest.get("deploymentPreflight"), dict) else {}
        candidates.extend([
            manifest.get("carGuid"),
            project.get("carGuid"),
            asset_status.get("carGuid"),
            preflight.get("carGuid"),
        ])

    external_json_path = project_folder / official_external_liveries.EXTERNAL_LIVERY_JSON
    if external_json_path.exists() and external_json_path.is_file():
        try:
            data, _warnings = official_external_liveries._read_external_livery_json(external_json_path)
        except Exception:
            data = {}
        if isinstance(data, dict):
            candidates.append(data.get("car_guid"))

    for value in candidates:
        guid = clean(value)
        if guid:
            return guid
    return ""


def _official_external_material_batch_from_saved_car(saved_car: dict[str, Any] | None, fallback: str = "EXT_SKIN") -> str:
    fallback = str(fallback or "").strip() or "EXT_SKIN"
    if not isinstance(saved_car, dict):
        return fallback
    for ref in saved_car.get("materialRefs") or []:
        stem = Path(str(ref or "").replace("\\", "/")).stem.strip()
        if stem.upper().startswith("EXT_SKIN"):
            return stem
    return fallback


def _official_external_entry_material_batch(entry: dict[str, Any], fallback: str = "EXT_SKIN") -> str:
    details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
    for key in ("materialBatch", "linkedSavedCarMaterialBatch", "skinMaterialBatch"):
        value = str(details.get(key) or "").strip()
        if value:
            return value
    saved_car = details.get("linkedSavedCar") if isinstance(details.get("linkedSavedCar"), dict) else {}
    return _official_external_material_batch_from_saved_car(saved_car, fallback=fallback)


def _official_external_entry_material_surfaces(entry: dict[str, Any]) -> list[dict[str, Any]]:
    details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
    stored = details.get("materialSurfaces") if isinstance(details.get("materialSurfaces"), list) else []
    if stored:
        return [dict(row) for row in stored if isinstance(row, dict)]
    saved_car = details.get("linkedSavedCar") if isinstance(details.get("linkedSavedCar"), dict) else {}
    if not isinstance(saved_car.get("materialRefs"), list) or not saved_car.get("materialRefs"):
        project_root = _entry_local_root(entry)
        saved_car_root = project_root / "SavedCar" if project_root else None
        if saved_car_root and saved_car_root.is_dir():
            candidate = next(iter(sorted(saved_car_root.glob("*.carfinalstatewithconsumable"), key=lambda path: path.name.lower())), None)
            if candidate is not None:
                inspection = official_saved_car_state.inspect_saved_car_file(candidate)
                if inspection.get("valid"):
                    saved_car = inspection
    game_root = normalize_game_root(str(entry.get("game_root") or get_default_root_folder()))
    return official_material_surfaces.detect_material_surfaces(saved_car, game_root)


def _official_external_entry_saved_car_body_color(entry: dict[str, Any], game_root: Path | None = None) -> dict[str, Any]:
    path = saved_car_body_color.find_saved_car(str(entry.get("car_model") or ""), str(entry.get("car_guid") or ""))
    if path is None:
        return {"supported": False, "available": False, "reason": "Active SavedCar was not found", "path": ""}
    root = game_root or normalize_game_root(str(entry.get("game_root") or get_default_root_folder()))
    return saved_car_body_color.inspect(path, root)


def _update_official_external_project_manifest(
    project_folder: Path,
    asset_status: dict[str, Any],
    preview_assets: dict[str, Any] | None = None,
) -> dict[str, Any]:
    manifest_path = project_folder / official_external_projects.PROJECT_MANIFEST
    manifest: dict[str, Any] = {}
    if manifest_path.exists() and manifest_path.is_file():
        try:
            loaded = json.loads(manifest_path.read_text(encoding="utf-8"))
            manifest = loaded if isinstance(loaded, dict) else {}
        except Exception:
            manifest = {}
    manifest.setdefault("formatVersion", 1)
    manifest["projectType"] = project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY
    manifest["officialExternalRole"] = official_external_workflow.ROLE_PROJECT
    manifest["deploymentTarget"] = official_external_workflow.DEPLOYMENT_TARGET_SAVED_GAMES_EXTERNAL_LIVERIES
    manifest["assetLayout"] = official_external_project_assets.asset_layout_manifest()
    manifest["assetStatus"] = asset_status
    if preview_assets is not None:
        manifest["previewAssets"] = preview_assets
    manifest["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    manifest_path.write_text(json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False), encoding="utf-8")
    return {
        "manifestPath": str(manifest_path),
        "manifest": manifest,
    }


def _read_official_external_project_deployment_preflight(project_folder: Path | None) -> dict[str, Any]:
    if not project_folder:
        return {}
    manifest_path = project_folder / official_external_projects.PROJECT_MANIFEST
    if not manifest_path.exists() or not manifest_path.is_file():
        return {}
    try:
        manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    except Exception:
        return {}
    if not isinstance(manifest, dict):
        return {}
    preflight = manifest.get("deploymentPreflight")
    return preflight if isinstance(preflight, dict) else {}


def inspect_official_external_project(skin_key: str) -> dict[str, Any]:
    entry, project_folder = _official_external_project_entry(skin_key)
    asset_status = official_external_project_assets.inspect_project(project_folder)
    return _json_ready({
        "skinKey": skin_key,
        "entry": entry,
        "projectFolder": str(project_folder),
        "assetStatus": asset_status,
        "assetLayout": official_external_project_assets.asset_layout_manifest(),
    })


def validate_official_external_livery(skin_key: str) -> dict[str, Any]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    if not _is_official_external_my_skin_entry(entry):
        raise ValueError("This My Liveries entry is not an Official external livery")
    inspection = official_external_library.inspect_entry(entry)
    validation = inspection.get("localValidation") if isinstance(inspection.get("localValidation"), dict) else {}
    return _json_ready({
        "skinKey": skin_key,
        "entry": entry,
        "inspection": inspection,
        "validation": validation,
        "localRoot": inspection.get("localRoot") or entry.get("local_root") or "",
        "role": official_external_workflow.role_for_entry(entry),
    })


def refresh_official_external_project_manifest(
    skin_key: str,
    car_guid: str | None = None,
    *,
    prepare_preview_assets: bool = True,
) -> dict[str, Any]:
    entry, project_folder = _official_external_project_entry(skin_key)
    _assert_my_skin_policy(entry, "canActivateInGame")
    guid = _official_external_project_car_guid(entry, project_folder, override=car_guid)
    refresh = official_external_project_assets.refresh_external_livery_json(
        project_folder,
        car_guid=guid,
        material_batch=_official_external_entry_material_batch(entry),
    )
    preview_assets = (
        official_external_project_assets.prepare_project_preview_assets(
            project_folder,
            entry,
            preview_assets_root=get_official_preview_assets_root(create=True),
            overwrite_uv=False,
        )
        if prepare_preview_assets
        else {"ready": False, "skipped": True, "reason": "package/runtime refresh does not use Preview Assets"}
    )
    manifest_update = _update_official_external_project_manifest(
        project_folder,
        refresh.get("assetStatus") if isinstance(refresh.get("assetStatus"), dict) else {},
        preview_assets=preview_assets if prepare_preview_assets else None,
    )
    return _json_ready({
        "skinKey": skin_key,
        "projectFolder": str(project_folder),
        **refresh,
        "previewAssets": preview_assets,
        **manifest_update,
    })


def preflight_official_external_project_deployment(
    skin_key: str,
    *,
    saved_games_ace_root: str | Path | None = None,
    car_guid: str | None = None,
    refresh_manifest: bool = True,
) -> dict[str, Any]:
    entry, project_folder = _official_external_deployment_entry(skin_key)
    _assert_my_skin_policy(entry, "canActivateInGame")
    resolved_guid = _official_external_project_car_guid(entry, project_folder, override=car_guid)
    if resolved_guid:
        entry["car_guid"] = resolved_guid
    role = official_external_workflow.role_for_entry(entry)
    if role == official_external_workflow.ROLE_PROJECT:
        refresh_result = (
            refresh_official_external_project_manifest(
                skin_key,
                car_guid=resolved_guid or car_guid,
                prepare_preview_assets=False,
            )
            if refresh_manifest
            else inspect_official_external_project(skin_key)
        )
    else:
        refresh_result = {
            "skinKey": skin_key,
            "projectFolder": str(project_folder),
            "assetStatus": official_external_project_assets.inspect_project(
                project_folder,
                material_batch=_official_external_entry_material_batch(entry),
            ),
            "readOnly": True,
        }
    preflight = official_external_deployment.preflight(
        entry,
        project_folder,
        saved_games_ace_root=saved_games_ace_root,
        saved_car_templates_root=get_official_saved_car_templates_root(create=False),
        refresh_result=refresh_result,
    )
    manifest_path = project_folder / official_external_projects.PROJECT_MANIFEST
    if manifest_path.exists() and manifest_path.is_file():
        try:
            manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
            if not isinstance(manifest, dict):
                manifest = {}
        except Exception:
            manifest = {}
        manifest["deploymentPreflight"] = preflight
        manifest["updatedAt"] = datetime.now().isoformat(timespec="seconds")
        manifest_path.write_text(json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False), encoding="utf-8")
    return _json_ready({
        "skinKey": skin_key,
        "projectFolder": str(project_folder),
        "refresh": refresh_result,
        "preflight": preflight,
    })


def get_official_external_disabled_root(create: bool = True) -> Path:
    root = get_user_data_root() / "OfficialExternalDisabled"
    if create:
        root.mkdir(parents=True, exist_ok=True)
    return root


def _official_external_entry_folder_names(entry: dict[str, Any]) -> set[str]:
    names: set[str] = set()
    details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
    for value in [
        details.get("externalFolderName"),
        details.get("plannedExternalFolder"),
        details.get("sourceFolderName"),
        entry.get("skin_id"),
    ]:
        text = str(value or "").strip()
        if text:
            names.add(text.lower())
    for value in [
        details.get("deploymentTargetFolder"),
        details.get("sourceFolder"),
        details.get("externalLiveryFolder"),
    ]:
        text = str(value or "").strip()
        if text:
            names.add(Path(text).name.lower())
    descriptor = details.get("descriptor") if isinstance(details.get("descriptor"), dict) else {}
    for value in [
        descriptor.get("folderName"),
        descriptor.get("externalFolderName"),
        descriptor.get("sourceFolderName"),
    ]:
        text = str(value or "").strip()
        if text:
            names.add(text.lower())
    return {name for name in names if name}


def _managed_official_external_folder_names(skins: list[dict[str, Any]]) -> set[str]:
    names: set[str] = set()
    for entry in skins:
        if not isinstance(entry, dict):
            continue
        if project_types.project_type_for_entry(entry) != project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
            continue
        if str(entry.get("managed_by") or "").strip().lower() != MY_SKIN_MANAGED_BY_LIVERYLAB and entry.get("liverylab_managed") is not True:
            continue
        names.update(_official_external_entry_folder_names(entry))
    return names


def _unique_disabled_external_livery_target(source: Path) -> Path:
    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    root = get_official_external_disabled_root(create=True) / stamp
    base = root / source.name
    target = base
    counter = 2
    while target.exists():
        target = root / f"{source.name}_{counter}"
        counter += 1
    return target


def _make_official_external_disabled_session(entry: dict[str, Any]) -> Path:
    skin_id = re.sub(r"[^0-9A-Za-z._-]+", "_", str(entry.get("skin_id") or "official_livery")).strip("_") or "official_livery"
    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    root = get_official_external_disabled_root(create=True)
    target = root / f"{stamp}__{skin_id}"
    index = 1
    while target.exists():
        index += 1
        target = root / f"{stamp}__{skin_id}_{index}"
    target.mkdir(parents=True, exist_ok=False)
    return target


def _disable_official_external_guid_conflicts(preflight: dict[str, Any], skins: list[dict[str, Any]]) -> dict[str, Any]:
    rows = preflight.get("guidConflictExternalLiveries") if isinstance(preflight.get("guidConflictExternalLiveries"), list) else []
    result: dict[str, Any] = {
        "checkedCount": len(rows),
        "disabled": [],
        "disabledCount": 0,
        "blocked": [],
        "blockedCount": 0,
        "disabledRoot": str(get_official_external_disabled_root(create=False)),
    }
    if not rows:
        return result

    external_root = Path(str(preflight.get("externalLiveriesRoot") or "")).expanduser()
    target_folder = Path(str(preflight.get("targetFolder") or "")).expanduser()
    try:
        external_root_resolved = external_root.resolve()
    except Exception:
        external_root_resolved = external_root
    try:
        target_resolved = target_folder.resolve()
    except Exception:
        target_resolved = target_folder
    managed_names = _managed_official_external_folder_names(skins)

    for row in rows:
        if not isinstance(row, dict):
            continue
        source = Path(str(row.get("folder") or "")).expanduser()
        folder_name = str(row.get("folderName") or source.name).strip()
        try:
            source_resolved = source.resolve()
            source_resolved.relative_to(external_root_resolved)
        except Exception:
            blocked = dict(row)
            blocked["reason"] = "Conflict folder is outside the ExternalLiveries root."
            result["blocked"].append(blocked)
            continue
        if source_resolved == target_resolved:
            continue
        if folder_name.lower() not in managed_names:
            blocked = dict(row)
            blocked["reason"] = "Conflict is not known as a LiveryLab-managed official external livery."
            result["blocked"].append(blocked)
            continue
        if not source.exists() or not source.is_dir():
            continue

        target = _unique_disabled_external_livery_target(source)
        target.parent.mkdir(parents=True, exist_ok=True)
        shutil.move(str(source), str(target))
        moved = dict(row)
        moved["disabledFrom"] = str(source)
        moved["disabledTo"] = str(target)
        moved["disabledAt"] = datetime.now().isoformat(timespec="seconds")
        result["disabled"].append(moved)

    result["disabledCount"] = len(result["disabled"])
    result["blockedCount"] = len(result["blocked"])
    if result["blocked"]:
        details = "; ".join(
            f"{row.get('folderName') or row.get('folder')}: {row.get('reason') or 'blocked'}"
            for row in result["blocked"][:5]
        )
        raise RuntimeError(
            "Official activation found another active ExternalLiveries folder with the same car_guid. "
            "LiveryLab only auto-disables folders it manages. "
            f"Disable the conflicting folder first: {details}"
        )
    return result


def _official_external_active_folder_candidates(
    entry: dict[str, Any],
    *,
    saved_games_ace_root: str | Path | None = None,
) -> list[Path]:
    details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
    descriptor = details.get("descriptor") if isinstance(details.get("descriptor"), dict) else {}
    ace_root = saved_games_paths.ace_root(saved_games_ace_root)
    external_root = project_capabilities.official_external_liveries_root(ace_root)
    raw_candidates = [
        details.get("deploymentTargetFolder"),
        descriptor.get("externalLiveryFolder"),
        descriptor.get("sourceFolder"),
    ]
    folder_names = [
        details.get("externalFolderName"),
        details.get("plannedExternalFolder"),
        details.get("sourceFolderName"),
        descriptor.get("folderName"),
        entry.get("skin_id"),
    ]
    for name in folder_names:
        text = str(name or "").strip()
        if text:
            raw_candidates.append(str(external_root / text))

    candidates: list[Path] = []
    seen: set[str] = set()
    for raw in raw_candidates:
        text = str(raw or "").strip()
        if not text:
            continue
        path = Path(text).expanduser()
        if not path.is_absolute():
            path = external_root / path
        key = str(path).lower()
        if key in seen:
            continue
        seen.add(key)
        candidates.append(path)
    return candidates


def _resolve_official_external_active_folder(
    entry: dict[str, Any],
    *,
    saved_games_ace_root: str | Path | None = None,
) -> Path:
    ace_root = saved_games_paths.ace_root(saved_games_ace_root)
    external_root = project_capabilities.official_external_liveries_root(ace_root)
    try:
        external_root_resolved = external_root.resolve()
    except Exception:
        external_root_resolved = external_root

    for candidate in _official_external_active_folder_candidates(entry, saved_games_ace_root=saved_games_ace_root):
        try:
            resolved = candidate.resolve()
            resolved.relative_to(external_root_resolved)
        except Exception:
            continue
        if resolved.exists() and resolved.is_dir():
            return resolved
    raise FileNotFoundError("Official ExternalLiveries deployment folder was not found in Saved Games.")


def _rollback_official_external_deactivation(
    *,
    target_folder: Path | None,
    disabled_target: Path | None,
    external_moved: bool,
    saved_car_archive: dict[str, Any],
    session_root: Path,
) -> list[str]:
    errors: list[str] = []
    if external_moved:
        if not disabled_target or not disabled_target.exists() or not target_folder:
            errors.append("ExternalLivery rollback source or target is missing.")
        elif target_folder.exists():
            errors.append(f"ExternalLivery rollback target already exists: {target_folder}")
        else:
            try:
                target_folder.parent.mkdir(parents=True, exist_ok=True)
                shutil.move(str(disabled_target), str(target_folder))
            except Exception as exc:
                errors.append(f"ExternalLivery rollback failed: {exc}")
    saved_car_rollback = official_saved_car_vault.rollback_archived_saved_cars(saved_car_archive)
    errors.extend(str(row) for row in (saved_car_rollback.get("errors") or []) if str(row).strip())
    if not errors:
        shutil.rmtree(session_root, ignore_errors=True)
    return errors


def deactivate_official_external_project(
    skin_key: str,
    *,
    saved_games_ace_root: str | Path | None = None,
) -> dict[str, Any]:
    if game_launcher.is_game_running():
        raise RuntimeError("Assetto Corsa EVO is running. Close the game before deactivating an Official livery.")
    entry, skins, index = _get_my_skin_entry(skin_key)
    if project_types.project_type_for_entry(entry) != project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
        raise ValueError("This My Liveries entry is not an Official external livery")

    now = datetime.now().isoformat(timespec="seconds")
    try:
        target_folder = _resolve_official_external_active_folder(entry, saved_games_ace_root=saved_games_ace_root)
    except FileNotFoundError as exc:
        target_folder = None
        missing_reason = str(exc)
    else:
        missing_reason = ""

    session_root = _make_official_external_disabled_session(entry)
    saved_car_archive: dict[str, Any] = {}
    disabled_target: Path | None = None
    external_moved = False
    try:
        saved_car_archive = official_saved_car_vault.archive_matching_saved_cars(
            entry,
            session_root,
            saved_games_ace_root=saved_games_ace_root,
        )
        if target_folder is not None:
            disabled_target = session_root / "ExternalLivery" / target_folder.name
            disabled_target.parent.mkdir(parents=True, exist_ok=True)
            shutil.move(str(target_folder), str(disabled_target))
            external_moved = True
        moved_saved_cars = int(saved_car_archive.get("movedCount") or 0)
        status = "disabled" if external_moved or moved_saved_cars else "already_inactive"
        deactivation = {
            "status": status,
            "disabledFrom": str(target_folder) if target_folder else "",
            "disabledTo": str(disabled_target) if disabled_target else (str(session_root) if moved_saved_cars else ""),
            "disabledAt": now,
            "alreadyMissing": status == "already_inactive",
            "reason": missing_reason if status == "already_inactive" else "",
            "externalMissingReason": missing_reason if target_folder is None else "",
            "vaultSession": str(session_root),
            "savedCars": saved_car_archive,
        }
        if status == "already_inactive":
            shutil.rmtree(session_root, ignore_errors=True)
            deactivation["vaultSession"] = ""
            deactivation["manifestPath"] = ""
        else:
            manifest_path = session_root / "deactivation_manifest.json"
            manifest = {
                "action": "deactivate_official_external_livery",
                "createdAt": now,
                "skinKey": entry.get("key") or skin_key,
                "skinId": entry.get("skin_id") or "",
                "carModel": entry.get("car_model") or "",
                "carGuid": entry.get("car_guid") or saved_car_archive.get("carGuid") or "",
                "externalLivery": {
                    "moved": external_moved,
                    "source": str(target_folder) if target_folder else "",
                    "target": str(disabled_target) if disabled_target else "",
                },
                "savedCars": saved_car_archive,
            }
            manifest_path.write_text(json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False), encoding="utf-8")
            deactivation["manifestPath"] = str(manifest_path)
    except Exception as exc:
        rollback_errors = _rollback_official_external_deactivation(
            target_folder=target_folder,
            disabled_target=disabled_target,
            external_moved=external_moved,
            saved_car_archive=saved_car_archive,
            session_root=session_root,
        )
        if rollback_errors:
            raise RuntimeError(f"Official deactivation failed and rollback was incomplete: {exc}; {'; '.join(rollback_errors)}") from exc
        raise

    details = dict(entry.get("official_external") or {}) if isinstance(entry.get("official_external"), dict) else {}
    details["deploymentStatus"] = deactivation["status"]
    details["deployed"] = False
    details["deactivatedAt"] = now
    if deactivation.get("status") != "already_inactive":
        details["lastDeactivation"] = deactivation
    if saved_car_archive.get("movedCount"):
        details["savedCarFound"] = False
        details["savedCarPaths"] = []
        generated_saved_car = details.get("generatedSavedCar") if isinstance(details.get("generatedSavedCar"), dict) else {}
        if generated_saved_car:
            generated_saved_car["deploymentStatus"] = "archived"
            generated_saved_car["lastDeployedPath"] = generated_saved_car.get("deployedPath") or ""
            generated_saved_car["deployedPath"] = ""
            details["generatedSavedCar"] = generated_saved_car
    entry["official_external"] = details
    entry["active"] = False
    entry["updated_at"] = now
    skins[index] = entry
    try:
        saved = save_my_skins(skins)
    except Exception as exc:
        rollback_errors = _rollback_official_external_deactivation(
            target_folder=target_folder,
            disabled_target=disabled_target,
            external_moved=external_moved,
            saved_car_archive=saved_car_archive,
            session_root=session_root,
        )
        if rollback_errors:
            raise RuntimeError(f"Official registry update failed and deactivation rollback was incomplete: {exc}; {'; '.join(rollback_errors)}") from exc
        raise

    project_folder = _entry_local_root(entry)
    manifest_warning = ""
    if project_folder and project_folder.exists() and project_folder.is_dir():
        manifest_path = project_folder / official_external_projects.PROJECT_MANIFEST
        if manifest_path.exists() and manifest_path.is_file():
            try:
                manifest_data = json.loads(manifest_path.read_text(encoding="utf-8"))
                manifest = manifest_data if isinstance(manifest_data, dict) else {}
                manifest["deploymentStatus"] = "disabled"
                manifest["lastDeactivation"] = deactivation
                manifest["updatedAt"] = now
                manifest_path.write_text(json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False), encoding="utf-8")
            except Exception as exc:
                manifest_warning = str(exc)

    return _json_ready({
        "skinKey": skin_key,
        "item": entry,
        "settingsPath": saved,
        "deactivation": deactivation,
        "disabledFrom": deactivation.get("disabledFrom") or "",
        "disabledTo": deactivation.get("disabledTo") or "",
        "savedCars": saved_car_archive,
        "vaultSession": deactivation.get("vaultSession") or "",
        "manifestWarning": manifest_warning,
    })


def activate_official_external_project(
    skin_key: str,
    *,
    saved_games_ace_root: str | Path | None = None,
    car_guid: str | None = None,
    refresh_manifest: bool = True,
) -> dict[str, Any]:
    if game_launcher.is_game_running():
        raise RuntimeError("Assetto Corsa EVO is running. Close the game before activating an Official livery.")
    entry, skins, index = _get_my_skin_entry(skin_key)
    if project_types.project_type_for_entry(entry) != project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
        raise ValueError("This My Liveries entry is not an Official external livery")
    if official_external_workflow.role_for_entry(entry) not in {
        official_external_workflow.ROLE_PROJECT,
        official_external_workflow.ROLE_LIBRARY,
    }:
        raise ValueError("This Official external livery cannot be activated from its current storage role")

    project_folder_for_guid = _official_external_deployable_folder(entry) or _livery_project_folder_for_entry(entry)
    resolved_guid = _official_external_project_car_guid(entry, project_folder_for_guid, override=car_guid)
    if resolved_guid:
        entry["car_guid"] = resolved_guid

    saved_car_repair: dict[str, Any] = {
        "status": "not_attempted",
        "reason": "Official runtime activation validates only the project-local or package-embedded SavedCar.",
    }

    game_root_text = str(entry.get("game_root") or get_default_root_folder() or "").strip()
    game_root = normalize_game_root(game_root_text) if game_root_text else Path()
    runtime_state = runtime_profiles.inspect_runtime_state(
        game_root,
        saved_games_ace_root=saved_games_ace_root,
        package_manifest_root=get_package_state_manifest_root(),
    )
    if runtime_state.get("readyForOfficialGameWrites") is not True:
        details = "\n".join(
            [*(f"Blocker: {row}" for row in runtime_state.get("blockers") or []),
             *(f"Warning: {row}" for row in runtime_state.get("warnings") or []),
             *(f"Signal: {row}" for row in runtime_state.get("reasons") or [])]
        )
        message = (
            "Official Activate in Game requires active content.kspkg and reachable Saved Games folders. "
            "Loose extracted content/cars is allowed and only reported as a diagnostic warning."
        )
        if details:
            message = f"{message}\n{details}"
        raise RuntimeError(message)

    preflight_result = preflight_official_external_project_deployment(
        skin_key,
        saved_games_ace_root=saved_games_ace_root,
        car_guid=resolved_guid or car_guid,
        refresh_manifest=refresh_manifest,
    )
    preflight = preflight_result.get("preflight") if isinstance(preflight_result.get("preflight"), dict) else {}
    conflict_cleanup = _disable_official_external_guid_conflicts(preflight, skins)
    entry_details = dict(entry.get("official_external")) if isinstance(entry.get("official_external"), dict) else {}
    last_deactivation = entry_details.get("lastDeactivation") if isinstance(entry_details.get("lastDeactivation"), dict) else {}
    saved_car_archive = last_deactivation.get("savedCars") if isinstance(last_deactivation.get("savedCars"), dict) else {}
    ignored_saved_car_archive: dict[str, Any] = {}
    disabled_root = get_official_external_disabled_root(create=False)
    saved_car_archive = official_saved_car_vault.rebase_archive_paths(
        saved_car_archive,
        vault_root=disabled_root,
    )
    archive_rows = saved_car_archive.get("moved") if isinstance(saved_car_archive.get("moved"), list) else []
    if archive_rows:
        archive_trust = official_saved_car_vault.archive_trust_status(
            saved_car_archive,
            vault_root=disabled_root,
            saved_games_ace_root=saved_games_ace_root,
        )
        if archive_trust.get("trusted") is not True:
            ignored_saved_car_archive = archive_trust
            saved_car_archive = {}
            last_deactivation = {}
            entry_details.pop("lastDeactivation", None)
            entry["official_external"] = entry_details
    if not (saved_car_archive.get("moved") if isinstance(saved_car_archive.get("moved"), list) else []):
        discovered_archive = official_saved_car_vault.find_latest_archive(
            disabled_root,
            resolved_guid or entry.get("car_guid") or car_guid,
        )
        if discovered_archive:
            discovered_saved_cars = discovered_archive.get("savedCars") if isinstance(discovered_archive.get("savedCars"), dict) else {}
            discovered_trust = official_saved_car_vault.archive_trust_status(
                discovered_saved_cars,
                vault_root=disabled_root,
                saved_games_ace_root=saved_games_ace_root,
            )
            if discovered_trust.get("trusted") is True:
                last_deactivation = {**discovered_archive, "discoveredByGuid": True}
                saved_car_archive = discovered_saved_cars
            else:
                ignored_saved_car_archive = discovered_trust
    saved_car_restore = official_saved_car_vault.restore_saved_cars(
        saved_car_archive,
        saved_games_ace_root=saved_games_ace_root,
    )
    try:
        deployment = official_external_deployment.apply_preflight(preflight)
    except Exception:
        official_saved_car_vault.rollback_restored_saved_cars(saved_car_restore)
        raise
    deployment["savedCarVaultRestore"] = saved_car_restore
    if ignored_saved_car_archive:
        deployment["ignoredSavedCarArchive"] = ignored_saved_car_archive

    now = datetime.now().isoformat(timespec="seconds")
    details = dict(entry.get("official_external") or {}) if isinstance(entry.get("official_external"), dict) else {}
    details["deploymentStatus"] = "deployed"
    details["deployed"] = True
    details["deployedAt"] = now
    details["deploymentTargetFolder"] = deployment.get("targetFolder") or preflight.get("targetFolder") or ""
    details["externalFolderName"] = preflight.get("externalFolderName") or details.get("externalFolderName") or ""
    details["guidConflictCleanup"] = conflict_cleanup
    if resolved_guid:
        details["carGuid"] = resolved_guid
    if official_external_workflow.role_for_entry(entry) == official_external_workflow.ROLE_LIBRARY:
        deployed_folder_name = Path(str(details.get("deploymentTargetFolder") or "")).name
        source_descriptor_id = official_external_workflow.external_descriptor_id(
            deployed_folder_name or details.get("externalFolderName"),
            resolved_guid or entry.get("car_guid") or details.get("carGuid"),
        )
        if source_descriptor_id:
            details["sourceDescriptorId"] = source_descriptor_id
    saved_car_deployment = deployment.get("savedCar") if isinstance(deployment.get("savedCar"), dict) else {}
    saved_car_target = str(saved_car_deployment.get("target") or "").strip()
    restored_saved_car_targets = [
        str(row.get("target") or "").strip()
        for row in [
            *(saved_car_restore.get("restored") if isinstance(saved_car_restore.get("restored"), list) else []),
            *(saved_car_restore.get("skipped") if isinstance(saved_car_restore.get("skipped"), list) else []),
        ]
        if str(row.get("target") or "").strip()
    ]
    saved_car_targets = list(dict.fromkeys([target for target in [saved_car_target, *restored_saved_car_targets] if target]))
    if saved_car_targets:
        details["linkedSavedCarGuid"] = resolved_guid
        details["savedCarFound"] = True
        details["savedCarPaths"] = saved_car_targets
        details["savedCarDeploymentStatus"] = str(
            saved_car_deployment.get("status")
            or saved_car_restore.get("status")
            or "installed"
        )
        generated_saved_car = details.get("generatedSavedCar") if isinstance(details.get("generatedSavedCar"), dict) else {}
        if generated_saved_car:
            generated_saved_car["deploymentStatus"] = details["savedCarDeploymentStatus"]
            generated_saved_car["deployedPath"] = saved_car_targets[0]
            generated_saved_car["deployedAt"] = now
            details["generatedSavedCar"] = generated_saved_car
    if last_deactivation:
        last_deactivation["savedCarsRestore"] = saved_car_restore
        last_deactivation["savedCarsRestoredAt"] = now
        last_deactivation["savedCarsArchiveStatus"] = (
            "restored"
            if saved_car_restore.get("status") == "restored"
            else "already_present"
            if saved_car_restore.get("status") == "already_present"
            else str(saved_car_restore.get("status") or "not_available")
        )
        details["lastDeactivation"] = last_deactivation
    entry["official_external"] = details
    if resolved_guid:
        entry["car_guid"] = resolved_guid
    entry["active"] = True
    entry["updated_at"] = now
    _clear_my_skin_game_sync(entry)
    skins[index] = entry
    saved = save_my_skins(skins)

    project_folder = Path(str(preflight_result.get("projectFolder") or entry.get("local_root") or "")).expanduser()
    manifest_path = project_folder / official_external_projects.PROJECT_MANIFEST
    if manifest_path.exists() and manifest_path.is_file():
        try:
            manifest_data = json.loads(manifest_path.read_text(encoding="utf-8"))
            manifest = manifest_data if isinstance(manifest_data, dict) else {}
        except Exception:
            manifest = {}
        manifest["deploymentPreflight"] = preflight
        manifest["deployment"] = deployment
        manifest["updatedAt"] = now
        manifest_path.write_text(json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False), encoding="utf-8")

    return _json_ready({
        "skinKey": skin_key,
        "item": entry,
        "settingsPath": saved,
        "runtime": runtime_state,
        "savedCarRepair": saved_car_repair,
        "preflight": preflight,
        "deployment": deployment,
        "savedCarVaultRestore": saved_car_restore,
        "ignoredSavedCarArchive": ignored_saved_car_archive,
        "guidConflictCleanup": conflict_cleanup,
        "targetFolder": deployment.get("targetFolder") or "",
        "copiedCount": deployment.get("copiedCount", 0),
        "deletedRuntimeFileCount": deployment.get("deletedRuntimeFileCount", 0),
    })


def list_official_saved_cars(
    *,
    saved_games_ace_root: str | Path | None = None,
    custom_only: bool = True,
) -> dict[str, Any]:
    return _json_ready(official_saved_cars.scan_saved_cars(
        saved_games_ace_root=saved_games_ace_root,
        project_entries=load_my_skins(),
        custom_only=custom_only,
    ))


def list_official_saved_car_templates(car_model: str = "") -> dict[str, Any]:
    return _json_ready(official_saved_car_templates.scan_templates(
        get_official_saved_car_templates_root(create=False),
        car_model=car_model,
    ))


def link_official_external_project_saved_car(
    skin_key: str,
    *,
    car_guid: str = "",
    saved_car_path: str | Path | None = None,
    saved_games_ace_root: str | Path | None = None,
) -> dict[str, Any]:
    entry, skins, index = _get_my_skin_entry(skin_key)
    if project_types.project_type_for_entry(entry) != project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
        raise ValueError("Only official external livery projects can be linked to SavedCars.")
    if official_external_workflow.role_for_entry(entry) != official_external_workflow.ROLE_PROJECT:
        raise ValueError("Only editable official external livery projects can be relinked.")
    project_folder = _entry_local_root(entry)
    if not project_folder or not project_folder.exists() or not project_folder.is_dir():
        raise FileNotFoundError("Official external project folder is missing.")

    saved_car: dict[str, Any] = {}
    if str(saved_car_path or "").strip():
        saved_car = official_saved_cars.saved_car_from_path(saved_car_path)
    guid = official_saved_cars.normalize_guid(car_guid) or str(saved_car.get("guid") or "")
    if not guid:
        raise ValueError("A valid SavedCar GUID is required.")

    scan = list_official_saved_cars(saved_games_ace_root=saved_games_ace_root, custom_only=False)
    matches = [
        row for row in scan.get("items", [])
        if isinstance(row, dict) and str(row.get("guid") or "").lower() == guid.lower()
    ]
    if matches:
        saved_car = matches[0]
    elif saved_car and saved_car.get("guid") == guid:
        matches = [saved_car]

    details = dict(entry.get("official_external") or {}) if isinstance(entry.get("official_external"), dict) else {}
    details["linkedSavedCarGuid"] = guid
    details["savedCarFound"] = bool(matches)
    details["savedCarPaths"] = [str(row.get("path") or "") for row in matches if str(row.get("path") or "").strip()]
    details["linkedSavedCar"] = saved_car if saved_car else (matches[0] if matches else {})
    material_batch = _official_external_material_batch_from_saved_car(details["linkedSavedCar"])
    details["materialBatch"] = material_batch
    details["linkedSavedCarMaterialBatch"] = material_batch
    entry["official_external"] = details
    entry["car_guid"] = guid
    skins[index] = entry
    settings_path = save_my_skins(skins)

    refresh = official_external_project_assets.refresh_external_livery_json(
        project_folder,
        car_guid=guid,
        material_batch=material_batch,
    )
    manifest_update = _update_official_external_project_manifest(project_folder, refresh.get("assetStatus") if isinstance(refresh.get("assetStatus"), dict) else {})
    manifest_path = project_folder / official_external_projects.PROJECT_MANIFEST
    if manifest_path.exists() and manifest_path.is_file():
        try:
            manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
            if not isinstance(manifest, dict):
                manifest = {}
        except Exception:
            manifest = {}
        project = manifest.get("project") if isinstance(manifest.get("project"), dict) else {}
        project["carGuid"] = guid
        project["materialBatch"] = material_batch
        manifest["project"] = project
        manifest["linkedSavedCar"] = details["linkedSavedCar"]
        manifest["materialBatch"] = material_batch
        manifest["updatedAt"] = datetime.now().isoformat(timespec="seconds")
        manifest_path.write_text(json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False), encoding="utf-8")

    validation = preflight_official_external_project_deployment(
        skin_key,
        saved_games_ace_root=saved_games_ace_root,
        car_guid=guid,
        refresh_manifest=True,
    )
    return _json_ready({
        "skinKey": skin_key,
        "carGuid": guid,
        "savedCar": details["linkedSavedCar"],
        "savedCarFound": bool(matches),
        "savedCarPaths": details["savedCarPaths"],
        "settingsPath": str(settings_path),
        "refresh": refresh,
        "manifest": manifest_update,
        "validation": validation,
    })


def _target_candidates_for_game_root(target: str | Path, root_path: Path) -> list[Path]:
    raw = str(target or "").strip()
    if not raw:
        return []
    path = Path(raw).expanduser()
    candidates = [path]
    if not path.is_absolute():
        candidates.insert(0, root_path / path)
    resolved: list[Path] = []
    for candidate in candidates:
        try:
            resolved.append(candidate.resolve())
        except Exception:
            resolved.append(candidate.absolute())
    return resolved


def guard_if_game_write_target(
    root: str | Path | None,
    operation: str,
    targets: list[str | Path] | tuple[str | Path, ...],
    require_loose_content: bool = True,
) -> dict[str, Any] | None:
    root_text = str(root or "").strip() or get_default_root_folder()
    if not root_text:
        return None
    root_path = normalize_game_root(root_text)
    guarded: list[Path] = []
    for target in targets:
        for candidate in _target_candidates_for_game_root(target, root_path):
            if _path_is_within(candidate, root_path):
                guarded.append(candidate)
                break
    if not guarded:
        return None
    return require_game_write_ready(root_path, operation, guarded, require_content_cars=require_loose_content)


def get_default_root_folder() -> str:
    if DEFAULT_ROOT_FOLDER.strip():
        return str(normalize_game_root(DEFAULT_ROOT_FOLDER.strip()))
    value = load_app_settings().get("default_root_folder", "")
    return str(normalize_game_root(str(value).strip())) if value else ""


def find_texconv_executable(preferred: str | Path | None = None) -> Path | None:
    candidates: list[Path] = []
    if preferred:
        candidates.append(Path(preferred).expanduser())
    settings_path = str(load_app_settings().get("texconv_path", "")).strip()
    if settings_path:
        candidates.append(Path(settings_path).expanduser())
    program_root = get_program_root()
    candidates.append(program_root / "tools" / "texconv.exe")
    if program_root.name == "_internal":
        candidates.append(program_root.parent / "tools" / "texconv.exe")
    candidates.append(Path.cwd() / "LiveryLab" / "tools" / "texconv.exe")
    candidates.append(Path.cwd() / "tools" / "texconv.exe")
    candidates.append(Path.cwd() / "texconv.exe")
    which_match = shutil.which("texconv.exe") or shutil.which("texconv")
    if which_match:
        candidates.append(Path(which_match))
    seen: set[str] = set()
    for candidate in candidates:
        try:
            resolved = candidate.resolve()
        except Exception:
            resolved = candidate.absolute()
        key = str(resolved).lower()
        if key in seen:
            continue
        seen.add(key)
        if resolved.is_file():
            return resolved
    return None


def get_texconv_path() -> str:
    found = find_texconv_executable()
    return str(found) if found else ""


def save_texconv_path(path: str | Path) -> Path:
    value = str(path or "").strip()
    if not value:
        raise ValueError("texconv path is empty")
    target = Path(value).expanduser()
    if not target.is_file():
        raise FileNotFoundError(f"texconv executable not found: {target}")
    settings = load_app_settings()
    settings["texconv_path"] = str(target)
    return save_app_settings(settings)


def load_browser_favorites() -> list[dict[str, str]]:
    settings = load_app_settings()
    raw = settings.get("browser_favorites", [])
    if not isinstance(raw, list):
        return []
    favorites: list[dict[str, str]] = []
    changed = False
    seen: set[tuple[str, str]] = set()
    for item in raw:
        if not isinstance(item, dict):
            continue
        name = str(item.get("name", "")).strip()
        raw_path = str(item.get("path", "")).strip()
        path = install_relocation.rebase_managed_path(
            raw_path,
            get_portable_storage_root(),
            expected_type="dir",
        )
        changed = changed or path != raw_path
        if not name or not path:
            continue
        key = (name.lower(), path.lower())
        if key in seen:
            continue
        seen.add(key)
        favorites.append({"name": name, "path": path})
    if changed:
        settings["browser_favorites"] = favorites
        try:
            save_app_settings(settings)
        except OSError:
            pass
    return favorites


def _browser_favorite_path_key(path: str | Path) -> str:
    candidate = Path(path).expanduser()
    try:
        candidate = candidate.resolve()
    except Exception:
        candidate = candidate.absolute()
    return norm_slashes(str(candidate)).lower()


def _unique_browser_favorite_name(base_name: str, favorites: list[dict[str, str]], keep_path_key: str | None = None) -> str:
    candidate = str(base_name or "").strip() or "Favorite"
    existing_names = {
        str(item.get("name", "")).strip().lower()
        for item in favorites
        if _browser_favorite_path_key(str(item.get("path", ""))) != (keep_path_key or "")
    }
    if candidate.lower() not in existing_names:
        return candidate
    root = candidate
    suffix = 2
    while True:
        candidate = f"{root} ({suffix})"
        if candidate.lower() not in existing_names:
            return candidate
        suffix += 1


def save_browser_favorites(favorites: list[dict[str, str]]) -> Path:
    cleaned: list[dict[str, str]] = []
    seen_paths: set[str] = set()
    for item in favorites:
        if not isinstance(item, dict):
            continue
        name = str(item.get("name", "")).strip()
        path = str(item.get("path", "")).strip()
        if not name or not path:
            continue
        path_key = _browser_favorite_path_key(path)
        if path_key in seen_paths:
            continue
        seen_paths.add(path_key)
        cleaned.append({"name": name, "path": path})
    settings = load_app_settings()
    settings["browser_favorites"] = cleaned
    return save_app_settings(settings)


def _coerce_my_skins_list(raw: Any) -> list[dict[str, Any]]:
    if not isinstance(raw, list):
        return []
    skins: list[dict[str, Any]] = []
    for item in raw:
        if isinstance(item, dict):
            skins.append(dict(item))
    return skins


def _read_my_skins_registry(path: Path) -> list[dict[str, Any]]:
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return []
    if isinstance(data, dict):
        return _coerce_my_skins_list(data.get("my_skins", []))
    return _coerce_my_skins_list(data)


def _skin_metadata_path(entry: dict[str, Any]) -> Path | None:
    raw = str(entry.get("local_root") or "").strip()
    if not raw:
        return None
    return Path(raw) / SKIN_METADATA_FILENAME


def _write_skin_metadata(entry: dict[str, Any]) -> Path | None:
    path = _skin_metadata_path(entry)
    if path is None:
        return None
    path.parent.mkdir(parents=True, exist_ok=True)
    payload = {
        "formatVersion": 1,
        "app": {"name": APP_NAME, "version": APP_VERSION},
        "updatedAt": datetime.now().isoformat(timespec="seconds"),
        "skin": _json_ready(entry),
    }
    path.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
    return path


def _migrate_legacy_my_skins_from_app_settings(registry_path: Path) -> list[dict[str, Any]]:
    settings = load_app_settings()
    legacy = _coerce_my_skins_list(settings.get("my_skins", []))
    if not legacy:
        if "my_skins" in settings:
            settings.pop("my_skins", None)
            try:
                save_app_settings(settings)
            except OSError:
                pass
        return []
    save_my_skins(legacy)
    settings.pop("my_skins", None)
    try:
        save_app_settings(settings)
    except OSError:
        pass
    return legacy


def load_my_skins() -> list[dict[str, Any]]:
    path = get_my_skins_registry_path()
    if path.exists():
        items = _read_my_skins_registry(path)
        migrate_local_skin_storage(items)
        migrate_my_skin_management_flags(items)
        return items
    items = _migrate_legacy_my_skins_from_app_settings(path)
    migrate_local_skin_storage(items)
    migrate_my_skin_management_flags(items)
    return items


def save_my_skins(items: list[dict[str, Any]]) -> Path:
    path = get_my_skins_registry_path()
    cleaned = _coerce_my_skins_list(items)
    payload = {
        "formatVersion": 1,
        "app": {"name": APP_NAME, "version": APP_VERSION},
        "updatedAt": datetime.now().isoformat(timespec="seconds"),
        "my_skins": _json_ready(cleaned),
    }
    path.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
    for item in cleaned:
        try:
            _write_skin_metadata(item)
        except Exception:
            pass
    return path


def online_league_overview(scope: str = workflow_scopes.SCOPE_OFFICIAL) -> dict[str, Any]:
    return online_league.overview(_online_league_roots(), scope)


def create_online_league_project(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    return online_league.create_project(_online_league_roots(), payload or {})


def update_online_league_project(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    return online_league.update_project(_online_league_roots(), payload or {})


def update_online_league_project_thumbnail(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    return online_league.update_project_thumbnail(_online_league_roots(), payload or {})


def load_online_league_project(scope: str, project_id: str) -> dict[str, Any]:
    return online_league.load_project(_online_league_roots(), scope, project_id)


def import_online_league_entry(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    return online_league.import_entry(_online_league_roots(), payload or {})


def update_online_league_entry(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    return online_league.update_entry(_online_league_roots(), payload or {})


def run_online_league_preflight(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    data = payload or {}
    return online_league.run_preflight(
        _online_league_roots(),
        str(data.get("workflowScope") or data.get("scope") or workflow_scopes.SCOPE_OFFICIAL),
        str(data.get("projectId") or data.get("id") or ""),
    )


def build_online_league_pack(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    return online_league.build_league_pack(_online_league_roots(), payload or {})


def inspect_online_league_pack(package_path: str | Path) -> dict[str, Any]:
    return online_league.inspect_league_pack(_online_league_roots(), package_path)


def import_online_league_pack(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    return online_league.import_league_pack(_online_league_roots(), payload or {})


def list_installed_online_league_packs(scope: str = workflow_scopes.SCOPE_OFFICIAL) -> dict[str, Any]:
    return online_league.list_installed_packs(_online_league_roots(), scope)


def preflight_online_league_pack_activation(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    data = payload if isinstance(payload, dict) else {}
    installed_id = str(data.get("installedId") or data.get("packId") or "").strip()
    saved_games_ace_root = str(data.get("savedGamesAceRoot") or "").strip() or None
    external_root = project_capabilities.official_external_liveries_root(saved_games_ace_root)
    return online_league.preflight_installed_pack_activation(_online_league_roots(), installed_id, external_root)


def activate_online_league_pack(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    if game_launcher.is_game_running():
        raise RuntimeError("Assetto Corsa EVO is running. Close the game before activating an Online League pack.")
    data = payload if isinstance(payload, dict) else {}
    installed_id = str(data.get("installedId") or data.get("packId") or "").strip()
    saved_games_ace_root = str(data.get("savedGamesAceRoot") or "").strip() or None
    external_root = project_capabilities.official_external_liveries_root(saved_games_ace_root)
    return online_league.activate_installed_pack(
        _online_league_roots(),
        installed_id,
        external_root,
        confirm_token=str(data.get("confirmToken") or ""),
    )


def preflight_online_league_pack_deactivation(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    data = payload if isinstance(payload, dict) else {}
    installed_id = str(data.get("installedId") or data.get("packId") or "").strip()
    saved_games_ace_root = str(data.get("savedGamesAceRoot") or "").strip() or None
    external_root = project_capabilities.official_external_liveries_root(saved_games_ace_root)
    return online_league.preflight_installed_pack_deactivation(_online_league_roots(), installed_id, external_root)


def deactivate_online_league_pack(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    if game_launcher.is_game_running():
        raise RuntimeError("Assetto Corsa EVO is running. Close the game before deactivating an Online League pack.")
    data = payload if isinstance(payload, dict) else {}
    installed_id = str(data.get("installedId") or data.get("packId") or "").strip()
    saved_games_ace_root = str(data.get("savedGamesAceRoot") or "").strip() or None
    external_root = project_capabilities.official_external_liveries_root(saved_games_ace_root)
    return online_league.deactivate_installed_pack(
        _online_league_roots(),
        installed_id,
        external_root,
        confirm_token=str(data.get("confirmToken") or ""),
    )


def preflight_online_league_pack_deletion(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    data = payload if isinstance(payload, dict) else {}
    installed_id = str(data.get("installedId") or data.get("packId") or "").strip()
    saved_games_ace_root = str(data.get("savedGamesAceRoot") or "").strip() or None
    external_root = project_capabilities.official_external_liveries_root(saved_games_ace_root)
    return online_league.preflight_installed_pack_deletion(_online_league_roots(), installed_id, external_root)


def delete_online_league_pack(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    if game_launcher.is_game_running():
        raise RuntimeError("Assetto Corsa EVO is running. Close the game before deleting an Online League pack.")
    data = payload if isinstance(payload, dict) else {}
    installed_id = str(data.get("installedId") or data.get("packId") or "").strip()
    saved_games_ace_root = str(data.get("savedGamesAceRoot") or "").strip() or None
    external_root = project_capabilities.official_external_liveries_root(saved_games_ace_root)
    return online_league.delete_installed_pack(
        _online_league_roots(),
        installed_id,
        external_root,
        confirm_token=str(data.get("confirmToken") or ""),
    )


def _is_legacy_local_skin_hash(value: str) -> bool:
    return bool(re.fullmatch(r"[0-9a-f]{10}", str(value or "").strip(), flags=re.IGNORECASE))


def _entry_direct_local_skin_root(entry: dict[str, Any]) -> Path | None:
    skin_id = str(entry.get("skin_id") or "").strip()
    if not skin_id:
        key = str(entry.get("key") or "").strip()
        if ":" in key:
            skin_id = key.split(":", 1)[1].strip()
    if not skin_id:
        return None
    car_content_path = str(entry.get("car_content_path") or "").strip()
    if not car_content_path:
        car_model = str(entry.get("car_model") or "").strip()
        car_content_path = f"content\\cars\\{car_model}" if car_model else "content\\cars\\car"
    return local_skin_root_for(car_content_path, skin_id)


def _merge_local_skin_folder(source: Path, target: Path) -> None:
    if not source.exists() or source.resolve() == target.resolve():
        return
    target.mkdir(parents=True, exist_ok=True)
    for item in source.iterdir():
        destination = target / item.name
        if item.is_dir():
            _merge_local_skin_folder(item, destination)
            try:
                item.rmdir()
            except OSError:
                pass
        elif not destination.exists():
            destination.parent.mkdir(parents=True, exist_ok=True)
            item.replace(destination)
    try:
        source.rmdir()
    except OSError:
        pass


def _legacy_local_skin_candidates(entry: dict[str, Any]) -> list[Path]:
    direct = _entry_direct_local_skin_root(entry)
    if direct is None:
        return []
    local_root = get_local_skins_root()
    car_dir = direct.parent.name
    skin_dir = direct.name
    candidates: list[Path] = []
    for root_hash in local_root.iterdir() if local_root.exists() else []:
        if root_hash.is_dir() and _is_legacy_local_skin_hash(root_hash.name):
            candidate = root_hash / car_dir / skin_dir
            if candidate.exists() and candidate.is_dir():
                candidates.append(candidate)
    return candidates


def _is_official_external_my_skin_entry(entry: dict[str, Any]) -> bool:
    return project_types.project_type_for_entry(entry) == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY


def _rebased_official_my_skin_local_root(entry: dict[str, Any]) -> Path | None:
    raw_root = str(entry.get("local_root") or "").strip()
    if raw_root:
        current = Path(raw_root).expanduser()
        if current.exists() and current.is_dir():
            return current

    role = official_external_workflow.role_for_entry(entry)
    if role == official_external_workflow.ROLE_PROJECT:
        root_name = official_storage.OFFICIAL_LIVERY_PROJECTS_DIRNAME
        current_root = get_official_livery_projects_root(create=False)
    elif role == official_external_workflow.ROLE_LIBRARY:
        root_name = official_storage.OFFICIAL_LIVERY_LIBRARY_DIRNAME
        current_root = get_official_livery_library_root(create=False)
    else:
        return None

    def valid_candidate(candidate: Path) -> bool:
        if not candidate.exists() or not candidate.is_dir():
            return False
        marker_names = {SKIN_METADATA_FILENAME, official_external_liveries.EXTERNAL_LIVERY_JSON}
        if role == official_external_workflow.ROLE_PROJECT:
            marker_names.add(official_external_projects.PROJECT_MANIFEST)
        return any((candidate / name).is_file() for name in marker_names)

    if raw_root:
        parts = list(Path(raw_root).parts)
        root_index = next(
            (index for index, part in enumerate(parts) if part.lower() == root_name.lower()),
            None,
        )
        if root_index is not None:
            suffix = parts[root_index + 1 :]
            candidate = current_root.joinpath(*suffix)
            if valid_candidate(candidate):
                return candidate

    if role == official_external_workflow.ROLE_PROJECT:
        car_model = preview3d.safe_segment(entry.get("car_model"), "car")
        skin_id = preview3d.safe_segment(entry.get("skin_id") or entry.get("display_name"), "skin")
        candidate = current_root / car_model / skin_id
        if valid_candidate(candidate):
            return candidate
    return None


def my_skin_uses_liverylab_safety(entry: dict[str, Any] | None) -> bool:
    return not _is_official_external_my_skin_entry(entry if isinstance(entry, dict) else {})


def migrate_local_skin_storage(items: list[dict[str, Any]] | None = None, save: bool = True) -> dict[str, Any]:
    skins = _coerce_my_skins_list(items) if items is not None else _read_my_skins_registry(get_my_skins_registry_path())
    changed = False
    migrated: list[dict[str, str]] = []
    for entry in skins:
        if _is_official_external_my_skin_entry(entry):
            raw_root = str(entry.get("local_root") or "").strip()
            rebased = _rebased_official_my_skin_local_root(entry)
            if rebased is not None and raw_root != str(rebased):
                entry["local_root"] = str(rebased)
                migrated.append({"from": raw_root, "to": str(rebased)})
                changed = True
            continue
        direct = _entry_direct_local_skin_root(entry)
        if direct is None:
            continue
        raw_root = str(entry.get("local_root") or "").strip()
        sources: list[Path] = []
        if raw_root:
            raw_path = Path(raw_root)
            if raw_path.exists() and raw_path.is_dir() and raw_path.resolve() != direct.resolve():
                sources.append(raw_path)
        for candidate in _legacy_local_skin_candidates(entry):
            if candidate.resolve() != direct.resolve() and candidate not in sources:
                sources.append(candidate)
        for source in sources:
            _merge_local_skin_folder(source, direct)
            migrated.append({"from": str(source), "to": str(direct)})
            changed = True
        if raw_root and raw_root != str(direct) and direct.exists():
            entry["local_root"] = str(direct)
            changed = True
        elif not raw_root and direct.exists() and sources:
            entry["local_root"] = str(direct)
            changed = True
    local_root = get_local_skins_root()
    for root_hash in local_root.iterdir() if local_root.exists() else []:
        if not (root_hash.is_dir() and _is_legacy_local_skin_hash(root_hash.name)):
            continue
        for car_dir in root_hash.iterdir():
            if not car_dir.is_dir():
                continue
            for skin_dir in car_dir.iterdir():
                if not skin_dir.is_dir():
                    continue
                target = local_root / car_dir.name / skin_dir.name
                _merge_local_skin_folder(skin_dir, target)
                migrated.append({"from": str(skin_dir), "to": str(target)})
                changed = True
            try:
                car_dir.rmdir()
            except OSError:
                pass
    for entry in skins:
        if _is_official_external_my_skin_entry(entry):
            continue
        if str(entry.get("local_root") or "").strip():
            continue
        direct = _entry_direct_local_skin_root(entry)
        if direct and direct.exists() and direct.is_dir():
            entry["local_root"] = str(direct)
            changed = True
    removed_hash_roots: list[str] = []
    for root_hash in local_root.iterdir() if local_root.exists() else []:
        if not (root_hash.is_dir() and _is_legacy_local_skin_hash(root_hash.name)):
            continue
        try:
            root_hash.rmdir()
            removed_hash_roots.append(str(root_hash))
            changed = True
        except OSError:
            pass
    if items is not None:
        items[:] = skins
    saved_path = save_my_skins(skins) if changed and save else None
    return {
        "changed": changed,
        "migrated": migrated,
        "removedHashRoots": removed_hash_roots,
        "saved": str(saved_path) if saved_path else "",
    }


def save_my_skin_metadata(entry: dict[str, Any]) -> Path | None:
    return _write_skin_metadata(entry)


def _my_skin_keys_for_livery_categories(scope: str = workflow_scopes.SCOPE_ALL) -> list[str]:
    clean_scope = workflow_scopes.normalize_scope(scope)
    return [
        str(item.get("key") or "").strip()
        for item in load_my_skins()
        if isinstance(item, dict)
        and str(item.get("key") or "").strip()
        and workflow_scopes.entry_matches_scope(item, clean_scope)
    ]


def load_livery_categories(scope: str = workflow_scopes.SCOPE_ALL) -> dict[str, Any]:
    clean_scope = workflow_scopes.normalize_scope(scope)
    return livery_categories.load_state(load_app_settings(), _my_skin_keys_for_livery_categories(clean_scope), clean_scope)


def create_livery_category(name: str, scope: str = workflow_scopes.SCOPE_ALL) -> dict[str, Any]:
    clean_scope = workflow_scopes.normalize_scope(scope)
    return livery_categories.create_category(load_app_settings(), save_app_settings, name, _my_skin_keys_for_livery_categories(clean_scope), clean_scope)


def update_livery_category(category_id: str, patch: dict[str, Any], scope: str = workflow_scopes.SCOPE_ALL) -> dict[str, Any]:
    clean_scope = workflow_scopes.normalize_scope(scope)
    return livery_categories.update_category(load_app_settings(), save_app_settings, category_id, patch or {}, _my_skin_keys_for_livery_categories(clean_scope), clean_scope)


def update_livery_category_settings(patch: dict[str, Any], scope: str = workflow_scopes.SCOPE_ALL) -> dict[str, Any]:
    clean_scope = workflow_scopes.normalize_scope(scope)
    return livery_categories.update_settings(load_app_settings(), save_app_settings, patch or {}, _my_skin_keys_for_livery_categories(clean_scope), clean_scope)


def delete_livery_category(category_id: str, scope: str = workflow_scopes.SCOPE_ALL) -> dict[str, Any]:
    clean_scope = workflow_scopes.normalize_scope(scope)
    return livery_categories.delete_category(load_app_settings(), save_app_settings, category_id, _my_skin_keys_for_livery_categories(clean_scope), clean_scope)


def assign_livery_category(skin_key: str, category_id: str, scope: str = workflow_scopes.SCOPE_ALL) -> dict[str, Any]:
    clean_scope = workflow_scopes.normalize_scope(scope)
    return livery_categories.assign_skin(load_app_settings(), save_app_settings, skin_key, category_id, _my_skin_keys_for_livery_categories(clean_scope), clean_scope)


def set_livery_favorite(skin_key: str, favorite: bool = True, scope: str = workflow_scopes.SCOPE_ALL) -> dict[str, Any]:
    clean_scope = workflow_scopes.normalize_scope(scope)
    return livery_categories.set_favorite(load_app_settings(), save_app_settings, skin_key, favorite, _my_skin_keys_for_livery_categories(clean_scope), clean_scope)


OFFICIAL_CREATOR_CAR_FAVORITES_SETTINGS_KEY = "official_creator_favorite_car_ids"
ADVANCED_CREATOR_CAR_FAVORITES_SETTINGS_KEY = "advanced_creator_favorite_car_ids"


def _normalize_creator_car_favorites(raw: Any, valid_car_ids: set[str] | None = None) -> list[str]:
    rows = raw if isinstance(raw, list) else []
    favorites: dict[str, str] = {}
    for item in rows:
        car_id = sanitize_slug(str(item or "").strip())
        if not car_id:
            continue
        if valid_car_ids is not None and car_id.lower() not in valid_car_ids:
            continue
        favorites[car_id.lower()] = car_id
    return sorted(favorites.values(), key=lambda value: value.lower())


def _normalize_official_creator_car_favorites(raw: Any, valid_car_ids: set[str] | None = None) -> list[str]:
    rows = raw if isinstance(raw, list) else []
    favorites: dict[str, str] = {}
    for item in rows:
        car_id = official_car_resources.safe_car_id(item, "")
        if not car_id:
            continue
        if valid_car_ids is not None and car_id.lower() not in valid_car_ids:
            continue
        favorites[car_id.lower()] = car_id
    return sorted(favorites.values(), key=lambda value: value.lower())


def load_official_creator_car_favorites() -> dict[str, Any]:
    status = get_official_car_resources_status()
    car_ids = {
        str(item.get("carId") or "").strip().lower()
        for item in status.get("creatorCars", [])
        if isinstance(item, dict) and str(item.get("carId") or "").strip()
    }
    settings = load_app_settings()
    favorites = _normalize_official_creator_car_favorites(settings.get(OFFICIAL_CREATOR_CAR_FAVORITES_SETTINGS_KEY), car_ids or None)
    return {
        "favorites": favorites,
        "carCount": len(car_ids),
    }


def set_official_creator_car_favorite(car_id: str, favorite: bool = True) -> dict[str, Any]:
    clean_car_id = official_car_resources.safe_car_id(car_id, "")
    if not clean_car_id:
        raise ValueError("Car id is required")
    settings = load_app_settings()
    favorites = _normalize_official_creator_car_favorites(settings.get(OFFICIAL_CREATOR_CAR_FAVORITES_SETTINGS_KEY))
    by_key = {item.lower(): item for item in favorites}
    if favorite:
        by_key[clean_car_id.lower()] = clean_car_id
    else:
        by_key.pop(clean_car_id.lower(), None)
    settings[OFFICIAL_CREATOR_CAR_FAVORITES_SETTINGS_KEY] = sorted(by_key.values(), key=lambda value: value.lower())
    saved = save_app_settings(settings)
    return {
        "favorites": settings[OFFICIAL_CREATOR_CAR_FAVORITES_SETTINGS_KEY],
        "carId": clean_car_id,
        "favorite": bool(favorite),
        "settingsPath": str(saved),
    }


def load_advanced_creator_car_favorites() -> dict[str, Any]:
    cached = load_car_livery_compatibility_cache(get_default_root_folder() or Path.cwd())
    analysis = cached.get("analysis") if isinstance(cached, dict) else None
    car_ids = {
        str(item.get("carId") or "").strip().lower()
        for item in (analysis or {}).get("advancedCreatorCars", [])
        if isinstance(item, dict) and str(item.get("carId") or "").strip()
    }
    settings = load_app_settings()
    favorites = _normalize_creator_car_favorites(settings.get(ADVANCED_CREATOR_CAR_FAVORITES_SETTINGS_KEY), car_ids or None)
    return {
        "favorites": favorites,
        "carCount": len(car_ids),
    }


def set_advanced_creator_car_favorite(car_id: str, favorite: bool = True) -> dict[str, Any]:
    clean_car_id = sanitize_slug(str(car_id or "").strip())
    if not clean_car_id:
        raise ValueError("Car id is required")
    settings = load_app_settings()
    favorites = _normalize_creator_car_favorites(settings.get(ADVANCED_CREATOR_CAR_FAVORITES_SETTINGS_KEY))
    by_key = {item.lower(): item for item in favorites}
    if favorite:
        by_key[clean_car_id.lower()] = clean_car_id
    else:
        by_key.pop(clean_car_id.lower(), None)
    settings[ADVANCED_CREATOR_CAR_FAVORITES_SETTINGS_KEY] = sorted(by_key.values(), key=lambda value: value.lower())
    saved = save_app_settings(settings)
    return {
        "favorites": settings[ADVANCED_CREATOR_CAR_FAVORITES_SETTINGS_KEY],
        "carId": clean_car_id,
        "favorite": bool(favorite),
        "settingsPath": str(saved),
    }



def set_my_skin_hidden(skin_key: str, hidden: bool = True) -> tuple[dict[str, Any], Path]:
    key = str(skin_key or "").strip().lower()
    if not key:
        raise ValueError("Skin key is empty")
    skins = load_my_skins()
    for item in skins:
        if str(item.get("key", "")).lower() == key:
            item["hidden"] = bool(hidden)
            saved = save_my_skins(skins)
            return dict(item), saved
    raise KeyError(f"My Skin not found: {skin_key}")


def _official_my_skin_owned_local_root(entry: dict[str, Any]) -> Path | None:
    if not _is_official_external_my_skin_entry(entry):
        return None
    raw = str(entry.get("local_root") or "").strip()
    if not raw:
        return None
    path = Path(raw).expanduser()
    if not path.exists() or not path.is_dir():
        return None
    role = official_external_workflow.role_for_entry(entry)
    allowed_roots = [get_official_local_skins_root(create=False)]
    if role == official_external_workflow.ROLE_PROJECT:
        allowed_roots.append(get_official_livery_projects_root(create=False))
    for root in allowed_roots:
        try:
            if _path_is_within(path.resolve(), root.resolve()):
                return path
        except Exception:
            continue
    return None


def _make_official_trash_session_dir(entry: dict[str, Any]) -> Path:
    skin_id = re.sub(r"[^0-9A-Za-z._-]+", "_", str(entry.get("skin_id") or "skin")).strip("_") or "skin"
    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    root = get_official_trash_root(create=True)
    target = root / f"{stamp}__{skin_id}"
    index = 1
    while target.exists():
        index += 1
        target = root / f"{stamp}__{skin_id}_{index}"
    target.mkdir(parents=True, exist_ok=True)
    return target


def _official_trash_session_dir(trash_id: str) -> Path:
    name = str(trash_id or "").strip()
    if not name or name in {".", ".."} or any(sep in name for sep in ("/", "\\")):
        raise ValueError("Official trash item id is invalid")
    root = get_official_trash_root(create=True).resolve(strict=False)
    target = (root / name).resolve(strict=False)
    try:
        target.relative_to(root)
    except ValueError as exc:
        raise ValueError("Official trash item path escapes the Official trash folder") from exc
    return target


def _read_official_my_skin_trash_manifest(trash_dir: Path) -> dict[str, Any]:
    manifest_path = trash_dir / "trash_manifest.json"
    if not manifest_path.exists() or not manifest_path.is_file():
        raise FileNotFoundError(f"Official trash manifest not found: {manifest_path}")
    data = json.loads(manifest_path.read_text(encoding="utf-8"))
    if not isinstance(data, dict) or data.get("action") != "trash_official_my_skin":
        raise ValueError(f"Trash item is not an Official My Liveries trash entry: {trash_dir.name}")
    skin = data.get("skin")
    if not isinstance(skin, dict):
        raise ValueError(f"Official trash manifest has no skin entry: {manifest_path}")
    return data


def _official_trash_payload_source(trash_dir: Path, manifest: dict[str, Any]) -> Path:
    payload = manifest.get("payload") if isinstance(manifest.get("payload"), dict) else {}
    rel = str(payload.get("trashRelative") or "").strip()
    if rel:
        return trash_dir / Path(*Path(rel).parts)
    return trash_dir / "payload" / "local_root"


def _official_trash_target_for_manifest(manifest: dict[str, Any]) -> Path:
    payload = manifest.get("payload") if isinstance(manifest.get("payload"), dict) else {}
    target = str(payload.get("target") or "").strip()
    if target:
        return Path(target).expanduser()
    entry = manifest.get("skin") if isinstance(manifest.get("skin"), dict) else {}
    return Path(str(entry.get("local_root") or "")).expanduser()


def _official_trashed_my_skin_summary(trash_dir: Path, manifest: dict[str, Any] | None = None) -> dict[str, Any]:
    data = manifest if isinstance(manifest, dict) else _read_official_my_skin_trash_manifest(trash_dir)
    entry = dict(data.get("skin") or {})
    source = _official_trash_payload_source(trash_dir, data)
    target = _official_trash_target_for_manifest(data)
    key = str(entry.get("key") or "").strip().lower()
    existing = next((row for row in load_my_skins() if str(row.get("key") or "").strip().lower() == key), None)
    conflicts: list[str] = []
    if existing:
        conflicts.append("This Official livery already exists in My Liveries.")
    if target.exists():
        conflicts.append(f"Official local folder already exists: {target}")
    incomplete = not source.exists()
    role = official_external_workflow.role_for_entry(entry)
    return {
        "id": trash_dir.name,
        "path": str(trash_dir),
        "displayName": str(entry.get("display_name") or entry.get("skin_id") or trash_dir.name),
        "skinId": str(entry.get("skin_id") or ""),
        "carModel": str(entry.get("car_model") or ""),
        "key": key,
        "officialExternalRole": role,
        "trashedAt": str(data.get("trashedAt") or ""),
        "localRootAvailable": source.exists(),
        "liveryProjectAvailable": bool(role == official_external_workflow.ROLE_PROJECT and source.exists()),
        "incomplete": incomplete,
        "canRestore": bool(key and source.exists() and not conflicts),
        "conflicts": conflicts,
        "localSource": str(source),
        "localTarget": str(target),
    }


def _trash_official_my_skin_entry(entry: dict[str, Any]) -> dict[str, Any]:
    source = _official_my_skin_owned_local_root(entry)
    if source is None:
        return {
            "moved": False,
            "reason": "Official livery has no LiveryLab-owned local folder to move",
        }
    trash_dir = _make_official_trash_session_dir(entry)
    payload_dir = trash_dir / "payload"
    payload_dir.mkdir(parents=True, exist_ok=True)
    target = payload_dir / "local_root"
    shutil.move(str(source), str(target))
    manifest = {
        "action": "trash_official_my_skin",
        "trashedAt": datetime.now().isoformat(timespec="seconds"),
        "skin": entry,
        "payload": {
            "kind": "localRoot",
            "role": official_external_workflow.role_for_entry(entry),
            "source": str(source),
            "target": str(source),
            "trashRelative": "payload/local_root",
        },
    }
    (trash_dir / "trash_manifest.json").write_text(json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False), encoding="utf-8")
    return _json_ready({
        "moved": True,
        "trashDir": trash_dir,
        "source": source,
        "payload": target,
        "role": official_external_workflow.role_for_entry(entry),
    })


def list_trashed_official_my_skins() -> dict[str, Any]:
    trash_root = get_official_trash_root(create=True)
    items: list[dict[str, Any]] = []
    for child in sorted(trash_root.iterdir(), key=lambda item: item.name.lower(), reverse=True):
        if not child.is_dir():
            continue
        try:
            manifest = _read_official_my_skin_trash_manifest(child)
            items.append(_json_ready(_official_trashed_my_skin_summary(child, manifest)))
        except Exception:
            continue
    return {"trashRoot": str(trash_root), "items": items}


def restore_trashed_official_my_skin(trash_id: str) -> dict[str, Any]:
    trash_dir = _official_trash_session_dir(trash_id)
    manifest = _read_official_my_skin_trash_manifest(trash_dir)
    info = _official_trashed_my_skin_summary(trash_dir, manifest)
    if info.get("conflicts"):
        raise FileExistsError("Official trash restore has conflicts:\n" + "\n".join(str(row) for row in info.get("conflicts") or []))
    if not info.get("canRestore"):
        raise FileNotFoundError("Official trash item does not contain restorable local livery files")
    source = Path(str(info.get("localSource") or ""))
    target = Path(str(info.get("localTarget") or ""))
    entry = dict(manifest.get("skin") or {})
    source_id = _official_external_removed_source_id(entry)
    was_suppressed = bool(source_id and source_id in _official_external_suppressed_source_ids())
    if source_id:
        _set_official_external_source_suppressed(source_id, False)
    try:
        target.parent.mkdir(parents=True, exist_ok=True)
        shutil.move(str(source), str(target))
        entry["local_root"] = str(target)
        entry["active"] = False
        entry["restored_at"] = datetime.now().isoformat(timespec="seconds")
        entry["trash_restored_from"] = trash_dir.name
        skins = load_my_skins()
        key = str(entry.get("key") or "").strip().lower()
        if any(str(item.get("key") or "").strip().lower() == key for item in skins):
            raise FileExistsError("This Official livery already exists in My Liveries")
        skins.append(entry)
        saved = save_my_skins(skins)
    except Exception:
        if source_id and was_suppressed:
            _set_official_external_source_suppressed(source_id, True)
        raise
    trash_cleanup_warning = ""
    try:
        shutil.rmtree(trash_dir)
    except Exception as exc:
        trash_cleanup_warning = str(exc)
    return _json_ready({
        "item": entry,
        "trashId": trash_dir.name,
        "trashDir": trash_dir,
        "restored": [{"kind": "officialLocalRoot", "source": source, "target": target}],
        "settingsPath": saved,
        "active": False,
        "officialExternalSourceRestored": source_id,
        "trashDeleted": not bool(trash_cleanup_warning),
        "trashCleanupWarning": trash_cleanup_warning,
    })


def delete_trashed_official_my_skin(trash_id: str) -> dict[str, Any]:
    trash_dir = _official_trash_session_dir(trash_id)
    manifest = _read_official_my_skin_trash_manifest(trash_dir)
    summary = _official_trashed_my_skin_summary(trash_dir, manifest)
    entry = manifest.get("skin") if isinstance(manifest.get("skin"), dict) else {}
    source_id = _official_external_removed_source_id(entry)
    source_is_current = bool(source_id and any(
        _official_external_source_id(item) == source_id
        for item in load_my_skins()
        if isinstance(item, dict)
    ))
    should_suppress = bool(source_id and not source_is_current)
    was_suppressed = bool(source_id and source_id in _official_external_suppressed_source_ids())
    file_count = sum(1 for child in trash_dir.rglob("*") if child.is_file())
    if should_suppress:
        _set_official_external_source_suppressed(source_id, True)
    try:
        shutil.rmtree(trash_dir)
    except Exception:
        if should_suppress and not was_suppressed:
            _set_official_external_source_suppressed(source_id, False)
        raise
    return _json_ready({
        "deleted": True,
        "trashId": trash_id,
        "trashDir": trash_dir,
        "fileCount": file_count,
        "item": summary,
        "officialExternalSourceSuppressed": source_id if should_suppress else "",
    })


def remove_my_skin(skin_key: str) -> tuple[dict[str, Any], Path]:
    key = str(skin_key or "").strip().lower()
    if not key:
        raise ValueError("Skin key is empty")
    skins = load_my_skins()
    kept: list[dict[str, Any]] = []
    removed: dict[str, Any] | None = None
    for item in skins:
        if removed is None and str(item.get("key", "")).lower() == key:
            removed = dict(item)
            continue
        kept.append(item)
    if removed is None:
        raise KeyError(f"My Skin not found: {skin_key}")
    _assert_my_skin_policy(removed, "canRemove")
    official_deactivation: dict[str, Any] = {}
    if _is_official_external_my_skin_entry(removed):
        official_deactivation = deactivate_official_external_project(skin_key)
        skins = load_my_skins()
        kept = []
        removed = None
        for item in skins:
            if removed is None and str(item.get("key", "")).lower() == key:
                removed = dict(item)
                continue
            kept.append(item)
        if removed is None:
            raise KeyError(f"My Skin disappeared after Official deactivation: {skin_key}")
        removed["officialDeactivation"] = official_deactivation.get("deactivation") or official_deactivation
    source_id = _official_external_removed_source_id(removed)
    was_suppressed = bool(source_id and source_id in _official_external_suppressed_source_ids())
    try:
        if source_id:
            _set_official_external_source_suppressed(source_id, True)
            removed["officialExternalSourceSuppressed"] = source_id
        if _is_official_external_my_skin_entry(removed):
            removed["officialTrash"] = _trash_official_my_skin_entry(removed)
        saved = save_my_skins(kept)
    except Exception:
        if source_id and not was_suppressed:
            _set_official_external_source_suppressed(source_id, False)
        raise
    if not my_skin_uses_liverylab_safety(removed):
        removed["game_root"] = ""
    return removed, saved


def _trash_root(game_root: Path) -> Path:
    return get_trash_root(game_root)


def _make_trash_session_dir(game_root: Path, entry: dict[str, Any]) -> Path:
    skin_id = re.sub(r"[^0-9A-Za-z._-]+", "_", str(entry.get("skin_id") or "skin")).strip("_") or "skin"
    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    target = _trash_root(game_root) / f"{stamp}__{skin_id}"
    index = 1
    while target.exists():
        index += 1
        target = _trash_root(game_root) / f"{stamp}__{skin_id}_{index}"
    target.mkdir(parents=True, exist_ok=True)
    return target


def _trash_session_dir(trash_id: str, game_root: str | Path | None = None) -> Path:
    name = str(trash_id or "").strip()
    if not name or name in {".", ".."} or any(sep in name for sep in ("/", "\\")):
        raise ValueError("Trash item id is invalid")
    root = get_trash_root(game_root or get_default_root_folder() or Path.cwd()).resolve(strict=False)
    target = (root / name).resolve(strict=False)
    try:
        target.relative_to(root)
    except ValueError as exc:
        raise ValueError("Trash item path escapes the LiveryLab trash folder") from exc
    return target


def _read_my_skin_trash_manifest(trash_dir: Path) -> dict[str, Any]:
    manifest_path = trash_dir / "trash_manifest.json"
    if not manifest_path.exists() or not manifest_path.is_file():
        raise FileNotFoundError(f"Trash manifest not found: {manifest_path}")
    data = json.loads(manifest_path.read_text(encoding="utf-8"))
    if not isinstance(data, dict) or data.get("action") != "trash_my_skin":
        raise ValueError(f"Trash item is not a My Liveries trash entry: {trash_dir.name}")
    skin = data.get("skin")
    if not isinstance(skin, dict):
        raise ValueError(f"Trash manifest has no skin entry: {manifest_path}")
    return data


def _trash_local_skin_source(trash_dir: Path, entry: dict[str, Any]) -> Path:
    car_model = str(entry.get("car_model") or "").strip()
    skin_id = str(entry.get("skin_id") or "").strip()
    car_content_path = str(entry.get("car_content_path") or "").strip() or f"content\\cars\\{car_model}"
    if car_model and skin_id:
        candidate = trash_dir / LIVERY_LIBRARY_DIRNAME / LOCAL_SKINS_DIRNAME / car_model / skin_id
        if candidate.exists():
            return candidate
    local_root = local_skin_root_for(car_content_path, skin_id or "skin")
    try:
        rel = _local_skin_storage_trash_relative(local_root)
        candidate = trash_dir / rel
        if candidate.exists():
            return candidate
    except Exception:
        pass
    return trash_dir / LIVERY_LIBRARY_DIRNAME / LOCAL_SKINS_DIRNAME / (car_model or "car") / (skin_id or "skin")


def _trash_livery_project_source(trash_dir: Path, entry: dict[str, Any]) -> Path:
    car_model = str(entry.get("car_model") or "").strip() or "car"
    skin_id = str(entry.get("skin_id") or "").strip() or "skin"
    return trash_dir / LIVERY_PROJECTS_DIRNAME / car_model / skin_id


def _trashed_my_skin_summary(trash_dir: Path, manifest: dict[str, Any] | None = None, root: str | Path | None = None) -> dict[str, Any]:
    data = manifest if isinstance(manifest, dict) else _read_my_skin_trash_manifest(trash_dir)
    entry = dict(data.get("skin") or {})
    game_root = normalize_game_root(root or get_default_root_folder() or entry.get("game_root") or Path.cwd())
    skin_id = str(entry.get("skin_id") or "").strip()
    car_model = str(entry.get("car_model") or "").strip()
    display_name = str(entry.get("display_name") or skin_id or trash_dir.name).strip()
    local_source = _trash_local_skin_source(trash_dir, entry)
    project_source = _trash_livery_project_source(trash_dir, entry)
    content_source = trash_dir / "content"
    car_content_path = str(entry.get("car_content_path") or "").strip() or f"content\\cars\\{car_model}"
    local_target = local_skin_root_for(car_content_path, skin_id or "skin")
    project_target = _livery_project_folder_for_entry({**entry, "game_root": str(game_root)})
    has_local_payload = local_source.exists() or content_source.exists()
    has_project_payload = project_source.exists()
    has_restore_payload = has_local_payload or has_project_payload
    existing = next((row for row in load_my_skins() if str(row.get("key") or "").lower() == str(entry.get("key") or "").lower()), None)
    conflicts: list[str] = []
    if has_restore_payload and existing:
        conflicts.append("This livery already exists in My Liveries.")
    if has_local_payload and local_target.exists():
        conflicts.append(f"Local skin folder already exists: {local_target}")
    if has_project_payload and project_target.exists():
        conflicts.append(f"LiveryProject folder already exists: {project_target}")
    incomplete = not has_restore_payload
    return {
        "id": trash_dir.name,
        "path": str(trash_dir),
        "displayName": display_name,
        "skinId": skin_id,
        "carModel": car_model,
        "key": str(entry.get("key") or "").strip().lower(),
        "trashedAt": str(data.get("trashedAt") or ""),
        "localSkinAvailable": has_local_payload,
        "liveryProjectAvailable": has_project_payload,
        "savedCarsQuarantined": len((data.get("savedcars_quarantine") or {}).get("moved", []) or []) if isinstance(data.get("savedcars_quarantine"), dict) else 0,
        "movedCount": len(data.get("moved") or []) if isinstance(data.get("moved"), list) else 0,
        "missingCount": len(data.get("missing") or []) if isinstance(data.get("missing"), list) else 0,
        "incomplete": incomplete,
        "canRestore": bool(skin_id and car_model and has_restore_payload and not conflicts),
        "conflicts": conflicts,
        "localSource": str(local_source),
        "contentSource": str(content_source),
        "projectSource": str(project_source),
        "localTarget": str(local_target),
        "projectTarget": str(project_target),
    }


def list_trashed_my_skins(root: str | Path | None = None) -> dict[str, Any]:
    trash_root = get_trash_root(root or get_default_root_folder() or Path.cwd())
    trash_root.mkdir(parents=True, exist_ok=True)
    items: list[dict[str, Any]] = []
    for child in sorted(trash_root.iterdir(), key=lambda item: item.name.lower(), reverse=True):
        if not child.is_dir():
            continue
        try:
            manifest = _read_my_skin_trash_manifest(child)
            items.append(_json_ready(_trashed_my_skin_summary(child, manifest, root)))
        except Exception:
            continue
    return {"trashRoot": str(trash_root), "items": items}


def inspect_trashed_my_skin(trash_id: str, root: str | Path | None = None) -> dict[str, Any]:
    trash_dir = _trash_session_dir(trash_id, root)
    manifest = _read_my_skin_trash_manifest(trash_dir)
    return _json_ready(_trashed_my_skin_summary(trash_dir, manifest, root))


def restore_trashed_my_skin(trash_id: str, root: str | Path | None = None) -> dict[str, Any]:
    trash_dir = _trash_session_dir(trash_id, root)
    manifest = _read_my_skin_trash_manifest(trash_dir)
    info = _trashed_my_skin_summary(trash_dir, manifest, root)
    if info.get("conflicts"):
        raise FileExistsError("Trash restore has conflicts:\n" + "\n".join(str(row) for row in info.get("conflicts") or []))
    if not info.get("canRestore"):
        raise FileNotFoundError("Trash item does not contain restorable local livery files")

    entry = dict(manifest.get("skin") or {})
    game_root = normalize_game_root(root or get_default_root_folder() or entry.get("game_root") or Path.cwd())
    local_source = Path(str(info.get("localSource") or ""))
    project_source = Path(str(info.get("projectSource") or ""))
    local_target = Path(str(info.get("localTarget") or ""))
    project_target = Path(str(info.get("projectTarget") or ""))
    restored: list[dict[str, Any]] = []

    if local_source.exists():
        local_target.parent.mkdir(parents=True, exist_ok=True)
        shutil.move(str(local_source), str(local_target))
        restored.append({"kind": "localSkin", "source": str(local_source), "target": str(local_target)})
    elif (trash_dir / "content").exists():
        local_target.mkdir(parents=True, exist_ok=True)
        shutil.move(str(trash_dir / "content"), str(local_target / "content"))
        restored.append({"kind": "content", "source": str(trash_dir / "content"), "target": str(local_target / "content")})

    if project_source.exists():
        project_target.parent.mkdir(parents=True, exist_ok=True)
        shutil.move(str(project_source), str(project_target))
        restored.append({"kind": "liveryProject", "source": str(project_source), "target": str(project_target)})

    entry["game_root"] = str(game_root)
    if local_target.exists():
        entry["local_root"] = str(local_target)
    entry["active"] = False
    entry["restored_at"] = datetime.now().isoformat(timespec="seconds")
    entry["trash_restored_from"] = trash_dir.name
    skins = load_my_skins()
    key = str(entry.get("key") or "").strip().lower()
    if any(str(item.get("key") or "").strip().lower() == key for item in skins):
        raise FileExistsError("This livery already exists in My Liveries")
    skins.append(entry)
    saved = save_my_skins(skins)
    trash_cleanup_warning = ""
    try:
        shutil.rmtree(trash_dir)
    except Exception as exc:
        trash_cleanup_warning = str(exc)
    return _json_ready({
        "item": entry,
        "trashId": trash_dir.name,
        "trashDir": trash_dir,
        "restored": restored,
        "settingsPath": saved,
        "active": False,
        "trashDeleted": not bool(trash_cleanup_warning),
        "trashCleanupWarning": trash_cleanup_warning,
    })


def delete_trashed_my_skin(trash_id: str, root: str | Path | None = None) -> dict[str, Any]:
    trash_dir = _trash_session_dir(trash_id, root)
    manifest = _read_my_skin_trash_manifest(trash_dir)
    summary = _trashed_my_skin_summary(trash_dir, manifest, root)
    file_count = sum(1 for child in trash_dir.rglob("*") if child.is_file())
    shutil.rmtree(trash_dir)
    return _json_ready({"deleted": True, "trashId": trash_id, "trashDir": trash_dir, "fileCount": file_count, "item": summary})


def _get_my_skin_entry(skin_key: str) -> tuple[dict[str, Any], list[dict[str, Any]], int]:
    key = str(skin_key or "").strip().lower()
    if not key:
        raise ValueError("Skin key is empty")
    skins = load_my_skins()
    for index, item in enumerate(skins):
        if str(item.get("key", "")).lower() == key:
            return dict(item), skins, index
    raise KeyError(f"My Skin not found: {skin_key}")


def _refresh_my_skin_policy_capabilities(entry: dict[str, Any]) -> dict[str, Any]:
    if not isinstance(entry, dict):
        return entry
    try:
        game_root_text = str(entry.get("game_root") or get_default_root_folder()).strip()
        game_root = normalize_game_root(game_root_text) if game_root_text else None
        entry["capabilities"] = _json_ready(project_capabilities.capabilities_for_entry(
            entry,
            game_root,
            livery_library_root=get_livery_library_root(),
            official_livery_library_root=get_official_livery_library_root(create=False),
        ))
        entry["capabilityDetails"] = _json_ready(project_capabilities.capability_details_for_entry(
            entry,
            game_root,
            livery_library_root=get_livery_library_root(),
            official_livery_library_root=get_official_livery_library_root(create=False),
        ))
    except Exception:
        pass
    return entry


def _assert_my_skin_policy(entry: dict[str, Any], permission: str) -> None:
    _refresh_my_skin_policy_capabilities(entry)
    skin_policy.assert_policy(entry, permission)


def _my_skin_game_sync_change_key(change: dict[str, Any]) -> str:
    workflow = str(change.get("workflow") or "").strip().lower()
    if workflow:
        return f"workflow:{workflow}"
    return f"reason:{str(change.get('reason') or '').strip().lower()}"


def _my_skin_game_sync_changes(sync: dict[str, Any], fallback_updated_at: str = "") -> list[dict[str, Any]]:
    changes: list[dict[str, Any]] = []
    seen: set[str] = set()
    seen_reasons: set[str] = set()
    raw_changes = sync.get("changes") if isinstance(sync, dict) else []
    for raw_change in raw_changes if isinstance(raw_changes, list) else []:
        if not isinstance(raw_change, dict):
            continue
        reason_text = str(raw_change.get("reason") or "").strip()
        workflow_text = str(raw_change.get("workflow") or "").strip()
        if not reason_text and not workflow_text:
            continue
        change = {
            "reason": reason_text or workflow_text,
            "workflow": workflow_text,
            "updated_at": str(raw_change.get("updated_at") or raw_change.get("updatedAt") or fallback_updated_at or "").strip(),
        }
        key = _my_skin_game_sync_change_key(change)
        reason_key = change["reason"].lower()
        if key in seen or reason_key in seen_reasons:
            continue
        seen.add(key)
        seen_reasons.add(reason_key)
        changes.append(change)
    reason_text = str(sync.get("reason") or "").strip() if isinstance(sync, dict) else ""
    workflow_text = str(sync.get("workflow") or "").strip() if isinstance(sync, dict) else ""
    if reason_text or workflow_text:
        fallback_change = {
            "reason": reason_text or workflow_text,
            "workflow": workflow_text,
            "updated_at": str(sync.get("updated_at") or fallback_updated_at or "").strip(),
        }
        key = _my_skin_game_sync_change_key(fallback_change)
        reason_key = fallback_change["reason"].lower()
        if key not in seen and reason_key not in seen_reasons:
            changes.append(fallback_change)
    return changes


def _merge_my_skin_game_sync_changes(*states: dict[str, Any]) -> list[dict[str, Any]]:
    merged: list[dict[str, Any]] = []
    index_by_key: dict[str, int] = {}
    index_by_reason: dict[str, int] = {}
    for state in states:
        if not isinstance(state, dict):
            continue
        for change in _my_skin_game_sync_changes(state):
            key = _my_skin_game_sync_change_key(change)
            reason_key = str(change.get("reason") or "").strip().lower()
            if key in index_by_key:
                merged[index_by_key[key]] = change
            elif reason_key and reason_key in index_by_reason:
                merged[index_by_reason[reason_key]] = change
                index_by_key[key] = index_by_reason[reason_key]
            else:
                index_by_key[key] = len(merged)
                if reason_key:
                    index_by_reason[reason_key] = len(merged)
                merged.append(change)
    return merged


def _my_skin_game_sync_state(reason: str, workflow: str = "", updated_at: str = "") -> dict[str, Any]:
    reason_text = str(reason or "").strip() or "Local skin files changed"
    workflow_text = str(workflow or "").strip()
    updated_text = str(updated_at or "").strip() or datetime.now().isoformat(timespec="seconds")
    return {
        "needs_update": True,
        "reason": reason_text,
        "workflow": workflow_text,
        "updated_at": updated_text,
        "changes": [{
            "reason": reason_text,
            "workflow": workflow_text,
            "updated_at": updated_text,
        }],
    }


def _merge_my_skin_game_sync_states(*states: dict[str, Any]) -> dict[str, Any]:
    valid_states = [state for state in states if isinstance(state, dict) and state.get("needs_update")]
    if not valid_states:
        return {}
    latest = valid_states[-1]
    merged = {
        "needs_update": True,
        "reason": str(latest.get("reason") or "Local skin files changed").strip(),
        "workflow": str(latest.get("workflow") or "").strip(),
        "updated_at": str(latest.get("updated_at") or datetime.now().isoformat(timespec="seconds")).strip(),
        "changes": _merge_my_skin_game_sync_changes(*valid_states),
    }
    return merged


def _mark_my_skin_game_sync_required(entry: dict[str, Any], reason: str, workflow: str = "") -> dict[str, Any]:
    existing = entry.get("game_sync") if isinstance(entry.get("game_sync"), dict) else {}
    entry["game_sync"] = _merge_my_skin_game_sync_states(
        existing,
        _my_skin_game_sync_state(reason, workflow),
    )
    return entry


def _clear_my_skin_game_sync(entry: dict[str, Any]) -> dict[str, Any]:
    entry.pop("game_sync", None)
    return entry


def _numberplate_variant_should_restore_visible_text(variant: dict[str, Any]) -> bool:
    if not isinstance(variant, dict):
        return False
    if variant.get("baked_template") is True or str(variant.get("source_type") or variant.get("numberplate_source") or "").strip().lower() == "template":
        return True
    if str(variant.get("template_id") or "").strip():
        return True
    color = _coerce_actor_numberplate_text_color(variant.get("text_color") or variant.get("textColor") or variant.get("overlay"))
    return bool(color and len(color) >= 4 and float(color[3]) < 0.999)


def _visible_actor_numberplate_text_color(actor_data: bytes, visual_data: bytes) -> list[float]:
    selector = extract_visual_numberplate_selector(visual_data)
    selected_indices = {
        int(index)
        for index in _actor_selected_numberplate_indices_by_surface(actor_data, selector).values()
        if index is not None and int(index) >= 0
    }
    plates = _actor_extract_numberplates(actor_data)
    candidates = [plate for plate in plates if int(plate.get("index") or 0) in selected_indices] if selected_indices else plates[:1]
    for plate in candidates:
        color = _actor_numberplate_text_color(plate)
        if color:
            visible = color[:4]
            visible[3] = 1.0
            return visible
    return [1.0, 1.0, 1.0, 1.0]


def _actor_selected_numberplate_indices(actor_data: bytes, visual_data: bytes) -> list[int]:
    selector = extract_visual_numberplate_selector(visual_data)
    selected = {
        int(index)
        for index in _actor_selected_numberplate_indices_by_surface(actor_data, selector).values()
        if index is not None and int(index) >= 0
    }
    for placement in _actor_selected_placement_records_by_surface(actor_data, selector).values():
        plate_index = placement.get("field_2")
        if plate_index:
            selected.add(int(plate_index) - 1)
    single = _actor_selected_numberplate_index(actor_data, selector)
    if single is not None and int(single) >= 0:
        selected.add(int(single))
    plates = _actor_extract_numberplates(actor_data)
    if not selected and plates:
        selected.add(0)
    return sorted(index for index in selected if 0 <= int(index) < len(plates))


def apply_visual_numberplate_visibility(
    actor_path: str | Path,
    visual_path: str | Path,
    visible: bool = True,
    writer: GameFileWriter | None = None,
) -> dict[str, Any]:
    actor = Path(actor_path)
    visual = Path(visual_path)
    if not actor.exists():
        raise FileNotFoundError(f"Actor not found: {actor}")
    if not visual.exists():
        raise FileNotFoundError(f"Visual preset not found: {visual}")
    actor_data = actor.read_bytes()
    visual_data = visual.read_bytes()
    plates = _actor_extract_numberplates(actor_data)
    selected_indices = _actor_selected_numberplate_indices(actor_data, visual_data)
    if not visible and plates:
        selected_indices = list(range(len(plates)))
    if not selected_indices:
        raise RuntimeError("No selected numberplate entry was found in the actor.")
    alpha = 1.0 if visible else 0.0
    opacity = 1.0 if visible else 0.0
    updates: list[dict[str, Any]] = []
    for index in selected_indices:
        plate = plates[index] if 0 <= index < len(plates) else {}
        color = _actor_numberplate_text_color(plate) or [1.0, 1.0, 1.0, 1.0]
        visible_color = list(color[:4])
        while len(visible_color) < 4:
            visible_color.append(1.0)
        visible_color[3] = alpha
        updates.append({"index": index, "opacity": opacity, "overlay": visible_color})
    new_actor_data, actor_changed = update_actor_numberplate_data(actor_data, numberplates=updates)
    actor_backup = None
    if actor_changed:
        actor_backup = _write_game_bytes(actor, new_actor_data, writer, label="actor numberplate visibility")
    variant_info = inspect_visual_numberplate_variant(actor, visual)
    variant_info["numberplate_hidden"] = not visible
    variant_info["numberplate_opacity"] = opacity
    variant_info["visibility_indices"] = selected_indices
    return {
        "actor_changed": actor_changed,
        "actor_backup": actor_backup,
        "visual_changed": False,
        "visual_backup": None,
        "variant": variant_info,
    }


def _hidden_numberplate_placement_updates(actor_data: bytes, selector: dict[int, int]) -> list[dict[str, Any]]:
    updates: list[dict[str, Any]] = []
    seen_indices: set[int] = set()
    selected = _actor_selected_placement_records_by_surface(actor_data, selector)
    for surface, placement in selected.items():
        if int(surface) not in {1, 3, 4}:
            continue
        try:
            placement_index = int(placement.get("index"))
        except Exception:
            continue
        if placement_index in seen_indices:
            continue
        seen_indices.add(placement_index)
        updates.append(
            {
                "index": placement_index,
                "scale": [
                    NUMBERPLATE_HIDDEN_PLACEMENT_SCALE,
                    NUMBERPLATE_HIDDEN_PLACEMENT_SCALE,
                    NUMBERPLATE_HIDDEN_PLACEMENT_SCALE,
                ],
            }
        )
    return updates


def apply_visual_numberplate_hidden_variant(
    actor_path: str | Path,
    visual_path: str | Path,
    previous_variant: dict[str, Any] | None = None,
    writer: GameFileWriter | None = None,
) -> dict[str, Any]:
    actor = Path(actor_path)
    visual = Path(visual_path)
    if not actor.exists():
        raise FileNotFoundError(f"Actor not found: {actor}")
    if not visual.exists():
        raise FileNotFoundError(f"Visual preset not found: {visual}")
    visual_data = visual.read_bytes()
    next_visual_data, visual_changed = advanced_numberplate_state.set_visual_selector(
        visual_data,
        {},
    )
    visual_backup = None
    if visual_changed:
        visual_backup = _write_game_bytes(
            visual,
            next_visual_data,
            writer,
            label="visual AC EVO no-numberplate selector",
        )

    previous = previous_variant if isinstance(previous_variant, dict) else {}
    surfaces = sorted(
        {
            int(value)
            for value in (previous.get("available_numberplate_surfaces") or [])
            if str(value).strip().isdigit()
        }
    )
    variant_info: dict[str, Any] = {
        "selector": {},
        "mode": "none",
        "selected_by_surface": {},
        "available_numberplate_surfaces": surfaces,
        "has_hood_numberplate": 1 in surfaces,
    }
    variant_info["source_type"] = "none"
    variant_info["numberplate_source"] = "none"
    variant_info["numberplate_hidden"] = True
    variant_info["numberplate_opacity"] = 0.0
    variant_info["selector_storage"] = "acevo_empty_message"
    variant_info["created_by"] = "AC EVO"
    variant_info["owns_actor_numberplate"] = False

    return {
        "actor_changed": False,
        "actor_backup": None,
        "visual_changed": visual_changed,
        "visual_backup": visual_backup,
        "variant": variant_info,
        "created_hidden_variant": False,
        "hidden_placement_updates": 0,
        "numberplate_updates": 0,
    }


def _disk_from_game_rel(game_root: Path, rel_path: str | None) -> Path | None:
    rel = str(rel_path or "").strip()
    if not rel:
        return None
    return game_root / Path(*split_content_path(normalize_game_rel_path(rel)))


def _skin_file_disk_path(game_root: Path, entry: dict[str, Any], rel_path: str | None, prefer_local: bool = True) -> Path | None:
    rel = str(rel_path or "").strip()
    if not rel:
        return None
    local_path = _local_skin_asset_path(entry, rel) if prefer_local else None
    if local_path and local_path.exists():
        return local_path
    return _disk_from_game_rel(game_root, rel)


def _ensure_local_skin_file_copy(game_root: Path, entry: dict[str, Any], rel_path: str | None, source: Path | None = None) -> Path | None:
    local_path = _local_skin_asset_path(entry, rel_path)
    if not local_path:
        return None
    if local_path.exists():
        return local_path
    source_path = source or _disk_from_game_rel(game_root, rel_path)
    if not source_path or not source_path.exists() or not source_path.is_file():
        return None
    local_path.parent.mkdir(parents=True, exist_ok=True)
    shutil.copy2(source_path, local_path)
    return local_path


def _entry_local_carfinalstate_paths(entry: dict[str, Any]) -> list[Path]:
    files = entry.get("files") if isinstance(entry.get("files"), dict) else {}
    rels: list[str] = []
    carfinalstates = files.get("carfinalstates") if isinstance(files.get("carfinalstates"), list) else []
    for value in (files.get("carfinalstate"), *carfinalstates):
        rel = normalize_game_rel_path(str(value or ""))
        if rel and rel.lower().endswith(".carfinalstate") and rel.lower() not in {row.lower() for row in rels}:
            rels.append(rel)
    paths: list[Path] = []
    for rel in rels:
        path = _local_skin_asset_path(entry, rel)
        if path and path.exists() and path.is_file():
            paths.append(path)
    return paths


def _sync_local_carfinalstate_numberplate_selectors(
    entry: dict[str, Any],
    visual_path: Path,
    writer: GameFileWriter,
) -> dict[str, Any]:
    visual_data = visual_path.read_bytes()
    changed_paths: list[str] = []
    backups: list[str] = []
    for path in _entry_local_carfinalstate_paths(entry):
        old_data = path.read_bytes()
        new_data, changed = advanced_numberplate_state.sync_carfinalstate_from_visual(
            old_data,
            visual_data,
        )
        if not changed:
            continue
        backup = _write_game_bytes(
            path,
            new_data,
            writer,
            label="local car final state numberplate selector",
        )
        changed_paths.append(str(path))
        if backup:
            backups.append(str(backup))
    return {
        "changed": bool(changed_paths),
        "changedCount": len(changed_paths),
        "paths": changed_paths,
        "backups": backups,
    }


# Disabled legacy Numberplate paths. The normal release workflow is PNG export
# plus AC EVO default/no-numberplate actor visibility; keep these gated unless
# the old overlay, skin-template bake or game-export workflows are intentionally restored.
NUMBERPLATE_OVERLAY_WORKFLOW_ENABLED = False
NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED = False
NUMBERPLATE_STUDIO_GAME_EXPORT_ENABLED = False
NUMBERPLATE_WORKFLOW_DISABLED_MESSAGE = (
    "The AC EVO numberplate overlay workflow is disabled. "
    "Export PNGs from Numberplate Studio and place them directly on the livery texture."
)
NUMBERPLATE_GAME_EXPORT_DISABLED_MESSAGE = (
    "Numberplate Studio game export is disabled. "
    "Use PNG export and place the result directly on the livery texture."
)
NUMBERPLATE_NONE_SOURCE_TYPES = {"none", "hidden", "off", "no_numberplate", "no-numberplate"}
NUMBERPLATE_STOCK_SOURCE_TYPES = {"", "acevo", "ac_evo", "ac-evo"}
NUMBERPLATE_HIDDEN_PLACEMENT_SCALE = 0.0001


def _source_numberplate_selector_for_entry(game_root: Path, entry: dict[str, Any]) -> tuple[dict[int, int], str]:
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    if source_skin:
        selector, selector_source = _source_numberplate_selector(game_root, source_skin)
        if selector:
            return selector, selector_source
    return {}, ""


def _cleanup_owned_numberplate_variant_for_source_reset(
    game_root: Path,
    entry: dict[str, Any],
    actor_path: str | Path,
    visual_path: str | Path,
    previous_variant: dict[str, Any],
    writer: GameFileWriter | None = None,
) -> dict[str, Any]:
    actor = Path(actor_path)
    visual = Path(visual_path)
    result: dict[str, Any] = {
        "attempted": False,
        "changed": False,
        "actor_changed": False,
        "visual_changed": False,
        "actor_backup": None,
        "visual_backup": None,
        "selector": {},
        "selector_source": "",
        "prune": {},
        "warnings": [],
    }
    if previous_variant.get("owns_actor_numberplate") is not True:
        result["skipped"] = "numberplate variant is not marked as owned by LiveryLab"
        return result
    target_indices = _numberplate_variant_indices(previous_variant)
    if not target_indices:
        result["skipped"] = "skin has no owned numberplate indices"
        return result

    result["attempted"] = True
    result["numberplate_indices"] = target_indices
    selector, selector_source = _source_numberplate_selector_for_entry(game_root, entry)
    result["selector"] = _json_ready(selector)
    result["selector_source"] = selector_source
    if not selector:
        result["warnings"].append("source skin has no usable numberplate selector")

    actor_data = actor.read_bytes()
    new_actor_data, prune_info = prune_actor_numberplate_variants_data(actor_data, target_indices)
    result["prune"] = _json_ready(prune_info)
    if prune_info.get("changed"):
        actor_backup = _write_game_bytes(actor, new_actor_data, writer, label="local actor numberplate variant cleanup")
        result["actor_changed"] = True
        result["actor_backup"] = actor_backup

    if selector:
        visual_changed, visual_backup = replace_visual_numberplate_selector_file(visual, selector, writer=writer)
        result["visual_changed"] = bool(visual_changed)
        result["visual_backup"] = visual_backup

    result["changed"] = bool(result.get("actor_changed") or result.get("visual_changed"))
    return result


def _numberplate_cleanup_actions(cleanup_result: dict[str, Any]) -> list[str]:
    if not cleanup_result.get("attempted"):
        return []
    actions: list[str] = []
    if cleanup_result.get("actor_changed"):
        actions.append(f"local actor LiveryLab numberplate entries removed (backup: {cleanup_result.get('actor_backup') or '-'})")
    else:
        prune = cleanup_result.get("prune") if isinstance(cleanup_result.get("prune"), dict) else {}
        skipped = str(prune.get("skipped") or cleanup_result.get("skipped") or "already clean")
        actions.append(f"local actor LiveryLab numberplate entries already clean ({skipped})")
    if cleanup_result.get("visual_changed"):
        actions.append(f"local visual numberplate selector reset to source skin (backup: {cleanup_result.get('visual_backup') or '-'})")
    elif cleanup_result.get("selector"):
        actions.append("local visual numberplate selector already matches source skin")
    for warning in cleanup_result.get("warnings") or []:
        actions.append(f"warning: {warning}")
    return actions


def normalize_game_rel_path(rel_path: str | None) -> str:
    parts = split_content_path(norm_slashes(str(rel_path or "")))
    while len(parts) >= 4 and [part.lower() for part in parts[:4]] == ["content", "cars", "content", "cars"]:
        parts = parts[2:]
    return norm_slashes("\\".join(parts))


def normalize_skin_asset_rel_path(rel_path: str | None) -> str:
    rel_norm = normalize_game_rel_path(rel_path)
    parts = split_content_path(rel_norm)
    lower_parts = [part.lower() for part in parts]
    for anchor in ("content", "system"):
        if anchor in lower_parts:
            index = lower_parts.index(anchor)
            return norm_slashes("\\".join(parts[index:]))
    return rel_norm


def _is_generated_thumbnail_ref(value: Any) -> bool:
    rel = normalize_game_rel_path(str(value or "").strip()).lower().replace("/", "\\")
    return "\\generated\\thumbnails\\" in f"\\{rel}"


def _strip_generated_thumbnail_file_refs(files: dict[str, Any]) -> dict[str, Any]:
    cleaned = dict(files or {})
    for kind in ("thumbnail", "thumbnail_mips"):
        if _is_generated_thumbnail_ref(cleaned.get(kind)):
            cleaned.pop(kind, None)
    for kind in ("thumbnails", "thumbnail_mips_list"):
        values = cleaned.get(kind)
        rows = values if isinstance(values, list) else ([values] if values else [])
        kept = [str(row) for row in rows if str(row or "").strip() and not _is_generated_thumbnail_ref(row)]
        if kept:
            cleaned[kind] = kept
        else:
            cleaned.pop(kind, None)
    return cleaned


def _skin_thumbnail_png_disk_path(game_root: Path, skin_folder_rel: str | None) -> Path | None:
    folder = _disk_from_game_rel(game_root, skin_folder_rel)
    if not folder:
        return None
    return folder / SKIN_THUMBNAIL_FILENAME


def _resolve_existing_skin_thumbnail_png(game_root: Path, skin_folder_rel: str | None) -> Path | None:
    folder = _disk_from_game_rel(game_root, skin_folder_rel)
    if not folder or not folder.exists():
        return None
    preferred = folder / SKIN_THUMBNAIL_FILENAME
    if preferred.exists():
        return preferred
    legacy = folder / "livery_preview.png"
    if legacy.exists():
        return legacy
    return None


def store_skin_thumbnail_png(preview_image: str | Path, game_root: Path, skin_folder_rel: str | None, writer: GameFileWriter | None = None) -> Path:
    preview_image = Path(preview_image)
    if not preview_image.exists():
        raise FileNotFoundError(f"Preview image not found: {preview_image}")
    target = _skin_thumbnail_png_disk_path(game_root, skin_folder_rel)
    if target is None:
        raise RuntimeError("Could not determine skin folder for thumbnail PNG")
    _copy_game_file(preview_image, target, writer, label="skin thumbnail png")
    return target


def ensure_skin_thumbnail_png_alias(game_root: Path, skin_folder_rel: str | None, writer: GameFileWriter | None = None) -> Path | None:
    existing = _resolve_existing_skin_thumbnail_png(game_root, skin_folder_rel)
    if existing is None:
        return None
    target = _skin_thumbnail_png_disk_path(game_root, skin_folder_rel)
    if target is None:
        return existing
    if existing.resolve() == target.resolve():
        return target
    if not target.exists():
        _copy_game_file(existing, target, writer, label="skin thumbnail png alias", backup=False)
    return target


def _resolve_my_skin_thumbnail_target(entry: dict[str, Any], game_root: Path) -> tuple[Path | None, str | None, str | None]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    for key_name, rel in list(files.items()):
        if isinstance(rel, str) and rel.strip():
            files[key_name] = normalize_game_rel_path(rel)
    entry["files"] = files
    existing_thumbnail = _disk_from_game_rel(game_root, str(files.get("thumbnail") or ""))
    if existing_thumbnail:
        return existing_thumbnail, str(files.get("mechanical_preset") or "").strip() or None, str(files.get("visual_preset") or "").strip() or None

    car_content_path = str(entry.get("car_content_path") or "").strip()
    mechanical_rel = str(files.get("mechanical_preset") or "").strip()
    visual_rel = str(files.get("visual_preset") or "").strip()

    visual_path = _disk_from_game_rel(game_root, visual_rel) if visual_rel else None
    actor_path = _disk_from_game_rel(game_root, str(files.get("actor") or "").strip())

    if (not mechanical_rel) and visual_path and visual_path.exists():
        mech = find_mechanicalpreset_for_visual(game_root, actor_path if actor_path and actor_path.exists() else None, visual_path, visual_rel)
        if mech and mech.exists():
            mechanical_rel = str(content_rel_from_disk(game_root, mech) or "").strip()

    if (not mechanical_rel or not visual_rel):
        finalstate_path = _disk_from_game_rel(game_root, str(files.get("carfinalstate") or "").strip())
        if finalstate_path and finalstate_path.exists():
            info = extract_carfinalstate_info(finalstate_path.read_bytes())
            if not mechanical_rel:
                mechanical_rel = str(info.get("mechanical_preset") or "").strip()
            if not visual_rel:
                visual_rel = str(info.get("visual_preset") or "").strip()

    if car_content_path and mechanical_rel and visual_rel:
        return generated_thumbnail_disk_path(game_root, car_content_path, mechanical_rel, visual_rel), mechanical_rel, visual_rel
    return None, mechanical_rel or None, visual_rel or None


def _remove_child_paths(paths: list[Path]) -> list[Path]:
    kept: list[Path] = []
    for path in sorted(paths, key=lambda row: (len(row.parts), str(row).lower())):
        if any(path != parent and path.is_relative_to(parent) for parent in kept):
            continue
        kept.append(path)
    return kept


def _move_path_to_trash(path: Path, game_root: Path, trash_dir: Path, writer: GameFileWriter | None = None) -> str:
    relative = path.resolve().relative_to(game_root.resolve())
    target = trash_dir / relative
    if writer:
        writer.move_path(path, target, label="trash My Skin path", backup=False)
    else:
        target.parent.mkdir(parents=True, exist_ok=True)
        shutil.move(str(path), str(target))
    return norm_slashes(str(relative))


def _local_skin_storage_trash_relative(path: Path) -> Path:
    local_root = get_local_skins_root().resolve()
    relative = path.resolve().relative_to(local_root)
    return Path(LIVERY_LIBRARY_DIRNAME) / LOCAL_SKINS_DIRNAME / relative


def _move_local_skin_storage_to_trash(path: Path, trash_dir: Path) -> str:
    relative = _local_skin_storage_trash_relative(path)
    target = trash_dir / relative
    target.parent.mkdir(parents=True, exist_ok=True)
    shutil.move(str(path), str(target))
    return norm_slashes(str(relative))


def _move_livery_project_to_trash(path: Path, trash_dir: Path, writer: GameFileWriter | None = None) -> str:
    projects_root = get_livery_projects_root().resolve()
    relative = path.resolve().relative_to(projects_root)
    target = trash_dir / LIVERY_PROJECTS_DIRNAME / relative
    if writer:
        writer.move_path(path, target, label="trash LiveryProject", backup=False)
    else:
        target.parent.mkdir(parents=True, exist_ok=True)
        shutil.move(str(path), str(target))
    return norm_slashes(str(Path(LIVERY_PROJECTS_DIRNAME) / relative))


def _is_livery_project_path(path: Path) -> bool:
    return _path_is_within(path, get_livery_projects_root())


def _is_local_skin_storage_path(path: Path) -> bool:
    return _path_is_within(path, get_local_skins_root())


def _livery_project_managed_trash_candidates(project_folder: Path) -> list[Path]:
    if not project_folder.exists() or not project_folder.is_dir():
        return []
    candidates: list[Path] = []
    for child in project_folder.iterdir():
        name_key = child.name.lower()
        if child.is_file() and name_key in LIVERY_PROJECT_MANAGED_FILENAMES:
            candidates.append(child.resolve())
        elif child.is_dir() and name_key in LIVERY_PROJECT_MANAGED_DIRNAMES:
            candidates.append(child.resolve())
    return _remove_child_paths(candidates)


def _livery_project_remaining_file_count(project_folder: Path) -> int:
    if not project_folder.exists() or not project_folder.is_dir():
        return 0
    try:
        return sum(1 for path in project_folder.rglob("*") if path.is_file())
    except Exception:
        return 0


def _json_ready(value: Any) -> Any:
    if isinstance(value, Path):
        return str(value)
    if isinstance(value, dict):
        return {str(key): _json_ready(val) for key, val in value.items()}
    if isinstance(value, (list, tuple)):
        return [_json_ready(item) for item in value]
    return value


def _entry_local_root(entry: dict[str, Any]) -> Path | None:
    raw = str(entry.get("local_root") or "").strip()
    if not raw:
        return None
    path = Path(raw)
    if path.exists() and path.is_dir():
        return path
    parts = list(path.parts)
    lower_parts = [part.lower() for part in parts]
    try:
        local_index = lower_parts.index(LOCAL_SKINS_DIRNAME.lower())
    except ValueError:
        return None
    suffix_parts = parts[local_index + 1:]
    if suffix_parts and _is_legacy_local_skin_hash(str(suffix_parts[0])):
        suffix_parts = suffix_parts[1:]
    suffix = Path(*suffix_parts) if suffix_parts else Path()
    migrated = get_local_skins_root() / suffix
    return migrated if migrated.exists() and migrated.is_dir() else None


def _normalize_my_skin_file_refs(entry: dict[str, Any]) -> bool:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    changed = False
    for kind, rel in list(files.items()):
        if not rel:
            continue
        if isinstance(rel, list):
            normalized: list[str] = []
            for row in rel:
                rel_norm = normalize_skin_asset_rel_path(str(row or ""))
                if rel_norm:
                    normalized.append(rel_norm)
            if normalized != rel:
                files[kind] = normalized
                changed = True
            continue
        rel_norm = normalize_skin_asset_rel_path(str(rel))
        if rel_norm != str(rel):
            files[kind] = rel_norm
            changed = True
    if changed:
        entry["files"] = files
    return changed


def _my_skin_ref_candidates(entry: dict[str, Any], game_root: Path, rel_path: str | None) -> list[str]:
    rel_norm = normalize_skin_asset_rel_path(rel_path)
    if not rel_norm:
        return []
    candidates = [rel_norm]
    local_root = _entry_local_root(entry)
    if local_root:
        try:
            local_prefix = local_root.resolve().relative_to(game_root.resolve())
            candidates.append(norm_slashes(str(local_prefix / Path(*split_content_path(rel_norm)))))
        except Exception:
            pass
    raw_norm = normalize_game_rel_path(rel_path)
    if raw_norm and raw_norm not in candidates:
        candidates.append(raw_norm)
    seen: set[str] = set()
    unique: list[str] = []
    for candidate in candidates:
        key = norm_slashes(candidate).lower()
        if key in seen:
            continue
        seen.add(key)
        unique.append(norm_slashes(candidate))
    return unique


def _legacy_ref_candidates(candidates: list[str]) -> list[str]:
    return [candidate for candidate in candidates if candidate.lower().startswith(("liverylab\\", "release\\", "liveryprojects\\"))]


def _remove_actor_cardesign_refs(actor_path: Path, refs: list[str], writer: GameFileWriter | None, label: str) -> bool:
    old_data = actor_path.read_bytes()
    new_data = old_data
    changed = False
    for ref in refs:
        new_data, did_change = remove_cardesign_from_actor_data(new_data, ref)
        changed = changed or did_change
    if not changed:
        return False
    _write_game_bytes(actor_path, new_data, writer, label=label)
    return True


def _remove_actor_visual_refs(actor_path: Path, refs: list[str], writer: GameFileWriter | None, label: str) -> bool:
    old_data = actor_path.read_bytes()
    new_data = old_data
    changed = False
    for ref in refs:
        new_data, did_change = remove_actor_resource_data(new_data, ref, 11)
        changed = changed or did_change
    if not changed:
        return False
    _write_game_bytes(actor_path, new_data, writer, label=label)
    return True


def _remove_mechanical_visual_refs(mechanical_path: Path, refs: list[str], writer: GameFileWriter | None, label: str) -> bool:
    old_data = mechanical_path.read_bytes()
    new_data = old_data
    changed = False
    for ref in refs:
        new_data, did_change = remove_visual_from_mechanicalpreset_data(new_data, ref)
        changed = changed or did_change
    if not changed:
        return False
    _write_game_bytes(mechanical_path, new_data, writer, label=label)
    return True


def _sync_local_my_skin_to_game(entry: dict[str, Any], game_root: Path, writer: GameFileWriter) -> list[str]:
    local_root = _entry_local_root(entry)
    if local_root is None:
        return []
    _normalize_my_skin_file_refs(entry)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    sync_kinds = {
        "cardesign",
        "design",
        "skin_folder",
        "material",
        "visual_preset",
        "carfinalstate",
        "carfinalstates",
        "thumbnail",
        "thumbnail_mips",
        "thumbnail_mips_list",
        "thumbnails",
        "thumbnail_png",
    }
    candidates: list[tuple[str, Path, Path]] = []
    for kind, rel_norm in _file_rel_values(files, sync_kinds):
        if not rel_norm:
            continue
        source = local_root / Path(*split_content_path(rel_norm))
        if not source.exists():
            continue
        target = game_root / Path(*split_content_path(rel_norm))
        candidates.append((kind, source, target))

    root_sources = _remove_child_paths([source for _kind, source, _target in candidates])
    root_source_keys = {str(path.resolve()).lower() for path in root_sources}
    synced: list[str] = []
    for kind, source, target in candidates:
        if str(source.resolve()).lower() not in root_source_keys:
            continue
        if source.is_dir():
            count = 0
            for child in source.rglob("*"):
                if not child.is_file():
                    continue
                child_target = target / child.relative_to(source)
                writer.copy_file(child, child_target, label=f"activate local {kind}")
                count += 1
            synced.append(f"{kind} synced ({count} files)")
        elif source.is_file():
            writer.copy_file(source, target, label=f"activate local {kind}")
            synced.append(f"{kind} synced")
    return synced


def _driver_profile_selection(entry: dict[str, Any]) -> dict[str, Any]:
    data = entry.get("driver_profile") if isinstance(entry.get("driver_profile"), dict) else {}
    return data if str(data.get("selection") or "").lower() == "profile" and str(data.get("profileId") or data.get("id") or "").strip() else {}


def _driver_profile_asset_path(profile: dict[str, Any], rel: str) -> Path:
    asset_root = Path(str(profile.get("assetRoot") or ""))
    return asset_root / "assets" / Path(*driver_skin.split_content_path(rel))


def _convert_driver_project_png_to_texturemips(texture: Path, mips: Path, png: Path, label: str = "driver texture") -> dict[str, Any]:
    desc = parse_texture_descriptor(texture)
    format_name, auto_note = choose_auto_format(texture, desc, mips.stat().st_size, label)
    tmp_dir = create_short_work_temp_dir("dt")
    try:
        dds_path = tmp_dir / "d.dds"
        convert_image_to_dds_with_texconv(
            png,
            dds_path,
            format_name,
            None,
            int(desc["mips"]),
            (int(desc["width"]), int(desc["height"])),
        )
        texturemips_data = texturemips_bytes_from_dds(texture, dds_path)
        backup = safe_write_bytes(mips, texturemips_data)
        return {
            "texture": str(texture),
            "texturemips": str(mips),
            "png": str(png),
            "label": label,
            "format": format_name,
            "note": auto_note,
            "mips": int(desc["mips"]),
            "width": int(desc["width"]),
            "height": int(desc["height"]),
            "bytes": len(texturemips_data),
            "backup": str(backup) if backup else "",
        }
    finally:
        cleanup_short_work_temp_dir(tmp_dir)


def _neutralize_driver_helmet_mask_texturemips(texture: Path, writer: GameFileWriter | None = None) -> dict[str, Any]:
    texture = Path(texture)
    mips = texture.with_suffix(".texturemips")
    if not texture.exists():
        return {"updated": False, "texture": str(texture), "reason": "texture missing"}
    if not mips.exists():
        return {"updated": False, "texture": str(texture), "texturemips": str(mips), "reason": "texturemips missing"}
    texturemips_data = _solid_bc1_texturemips_for_descriptor(texture, mips, "black")
    if mips.read_bytes() == texturemips_data:
        return {"updated": False, "texture": str(texture), "texturemips": str(mips), "reason": "current"}
    backup = _write_game_bytes(mips, texturemips_data, writer, label="driver neutral mask", backup=True)
    return {
        "updated": True,
        "texture": str(texture),
        "texturemips": str(mips),
        "backup": str(backup) if backup else "",
        "bytes": len(texturemips_data),
    }


def _is_driver_mask_texture_path(path: Path) -> bool:
    stem = path.stem.lower()
    lower = str(path).lower()
    return (
        path.suffix.lower() == ".texture"
        and any(token in lower for token in ("helmet", "suit", "glove"))
        and (stem.endswith("_d") or stem.endswith("_d_2"))
    )


def _driver_profile_from_selection(selection: dict[str, Any], car_model: str | None = None) -> dict[str, Any] | None:
    def with_selection_project_path(profile: dict[str, Any]) -> dict[str, Any]:
        project_path_text = str(selection.get("projectPath") or "").strip()
        if project_path_text:
            project_path = Path(project_path_text).expanduser()
            profile["projectPath"] = str(project_path)
            profile["projectRoot"] = str(project_path.parent)
            profile["projectAssetRoot"] = str(project_path.parent / "assets")
        return profile

    manifest_path_text = str(selection.get("manifestPath") or "").strip()
    if manifest_path_text:
        manifest_path = Path(manifest_path_text).expanduser()
        if manifest_path.exists() and manifest_path.is_file():
            try:
                profile = json.loads(manifest_path.read_text(encoding="utf-8"))
            except Exception:
                profile = None
            if isinstance(profile, dict):
                profile["manifestPath"] = str(manifest_path)
                profile["assetRoot"] = str(manifest_path.parent)
                return with_selection_project_path(driver_projects.attach_project_info(get_livery_projects_root(), profile))
    profile_id = str(selection.get("profileId") or selection.get("id") or "").strip()
    profile = driver_library.get_driver_profile(get_livery_library_root(), profile_id, car_model or None)
    return with_selection_project_path(driver_projects.attach_project_info(get_livery_projects_root(), profile)) if profile else None


def _iso_timestamp(value: str | None) -> float:
    text = str(value or "").strip()
    if not text:
        return 0.0
    try:
        return datetime.fromisoformat(text).timestamp()
    except Exception:
        return 0.0


def _driver_project_latest_source_mtime(project: dict[str, Any]) -> float:
    project_root_text = str(project.get("projectRoot") or "").strip()
    project_root = Path(project_root_text).expanduser() if project_root_text else None
    if not project_root:
        return 0.0
    paths: list[Path] = []
    for name in project.get("sourceFiles") or []:
        if str(name or "").strip():
            paths.append(project_root / str(name))
    for source in project.get("textureSources") or []:
        if not isinstance(source, dict):
            continue
        source_png = str(source.get("sourcePng") or "").strip()
        source_png_path = str(source.get("sourcePngPath") or "").strip()
        if source_png_path:
            paths.append(Path(source_png_path).expanduser())
        elif source_png:
            paths.append(project_root / source_png)
    mtimes = [path.stat().st_mtime for path in paths if path.exists() and path.is_file()]
    return max(mtimes) if mtimes else 0.0


def _driver_project_game_sync_state_for_entry(entry: dict[str, Any]) -> dict[str, Any]:
    selection = _driver_profile_selection(entry)
    if not selection:
        return {}
    profile = _driver_profile_from_selection(selection, str(entry.get("car_model") or ""))
    if not profile:
        return {}
    project = driver_projects.read_project(str(profile.get("projectPath") or ""))
    if not project:
        return {}
    applied_at = _iso_timestamp(str(selection.get("appliedAt") or ""))
    profile_updated_at = max(
        _iso_timestamp(str(profile.get("updatedAt") or "")),
        _iso_timestamp(str(profile.get("projectSyncedAt") or "")),
    )
    if applied_at > 0 and profile_updated_at > applied_at + 0.001:
        return _my_skin_game_sync_state(
            "Driver profile changed",
            "driver_profile",
            datetime.fromtimestamp(profile_updated_at).isoformat(timespec="seconds"),
        )
    latest_source_mtime = _driver_project_latest_source_mtime(project)
    if latest_source_mtime <= 0:
        return {}
    synced_at = _iso_timestamp(str(profile.get("projectSyncedAt") or ""))
    if synced_at <= 0:
        synced_at = applied_at
    if synced_at > 0 and latest_source_mtime <= synced_at + 0.001:
        return {}
    return _my_skin_game_sync_state(
        "Driver project PNG changed",
        "driver_project_png",
        datetime.fromtimestamp(latest_source_mtime).isoformat(timespec="seconds"),
    )


def _driver_material_refs_by_category(driver_data: bytes, categories: set[str]) -> dict[str, str]:
    result: dict[str, str] = {}
    for material_ref in driver_skin.extract_driverfinalstate_material_refs(driver_data):
        category = driver_skin.classify_driver_ref(material_ref)
        if category in categories and category not in result:
            result[category] = driver_skin.normalize_game_rel_path(material_ref)
    return result


def _repair_driver_profile_layer_materials(profile: dict[str, Any], game_root: Path) -> dict[str, Any]:
    profile_root_text = str(profile.get("assetRoot") or "").strip()
    if not profile_root_text:
        return {"changed": False, "skipped": "driver profile asset root missing", "items": []}
    profile_root = Path(profile_root_text).expanduser()
    profile_asset_root = profile_root / "assets"
    local_driver_ref = driver_skin.normalize_game_rel_path(str(profile.get("localDriverFinalState") or ""))
    source_driver_ref = driver_skin.normalize_game_rel_path(
        str(profile.get("driverFinalState") or (profile.get("signature") if isinstance(profile.get("signature"), dict) else {}).get("driverfinalstate") or "")
    )
    if not local_driver_ref or not source_driver_ref or local_driver_ref.lower() == source_driver_ref.lower():
        return {"changed": False, "skipped": "driver profile has no separate source driver final state", "items": []}

    local_driver_path = profile_asset_root / Path(*split_content_path(local_driver_ref))
    source_driver_path = game_root / Path(*split_content_path(source_driver_ref))
    if not local_driver_path.exists() or not source_driver_path.exists():
        return {
            "changed": False,
            "skipped": "driver final state source missing",
            "localDriverFinalState": str(local_driver_path),
            "sourceDriverFinalState": str(source_driver_path),
            "items": [],
        }

    categories = {"suit", "gloves"}
    local_materials = _driver_material_refs_by_category(local_driver_path.read_bytes(), categories)
    source_materials = _driver_material_refs_by_category(source_driver_path.read_bytes(), categories)
    items: list[dict[str, Any]] = []
    for category in sorted(categories):
        local_material_ref = local_materials.get(category, "")
        source_material_ref = source_materials.get(category, "")
        item: dict[str, Any] = {
            "category": category,
            "material": local_material_ref,
            "sourceMaterial": source_material_ref,
            "status": "pending",
        }
        if not local_material_ref or not source_material_ref:
            item.update({"status": "skipped", "reason": "material reference missing"})
            items.append(item)
            continue
        local_material_path = profile_asset_root / Path(*split_content_path(local_material_ref))
        source_material_path = game_root / Path(*split_content_path(source_material_ref))
        if not local_material_path.exists() or not source_material_path.exists():
            item.update({
                "status": "skipped",
                "reason": "material file missing",
                "materialPath": str(local_material_path),
                "sourceMaterialPath": str(source_material_path),
            })
            items.append(item)
            continue
        raw = local_material_path.read_bytes()
        new_raw = driver_skin.restore_material_parameters_from_source_data(
            raw,
            source_material_path.read_bytes(),
            driver_skin.DRIVER_LAYER_REPAIR_PARAMETER_NAMES,
            remove_missing=True,
        )
        neutral_parameters = driver_skin.png_only_neutral_parameters_for_category(category)
        new_raw = driver_skin.set_material_parameters_data(new_raw, neutral_parameters)
        if new_raw == raw:
            item.update({"status": "skipped", "reason": "material layer parameters already current"})
        else:
            local_material_path.write_bytes(new_raw)
            item.update({
                "status": "updated",
                "materialPath": str(local_material_path),
                "sourceMaterialPath": str(source_material_path),
                "bytes": len(new_raw),
            })
        items.append(item)
    return {
        "changed": any(item.get("status") == "updated" for item in items),
        "updatedCount": sum(1 for item in items if item.get("status") == "updated"),
        "skippedCount": sum(1 for item in items if item.get("status") == "skipped"),
        "items": _json_ready(items),
    }


def _sync_driver_profile_assets_for_entry(entry: dict[str, Any], game_root: Path, writer: GameFileWriter) -> dict[str, Any]:
    selection = _driver_profile_selection(entry)
    if not selection:
        return {"changed": False, "skipped": "no driver profile selected"}

    profile_id = str(selection.get("profileId") or selection.get("id") or "").strip()
    car_model = str(selection.get("carModel") or entry.get("car_model") or "").strip()
    driver_library.require_driver_car_supported(car_model, entry.get("car_content_path"))
    profile = _driver_profile_from_selection(selection, car_model)
    if not profile:
        raise FileNotFoundError(f"Driver profile not found: {profile_id}")
    project_sync = driver_projects.sync_project_to_profile(
        profile,
        png_to_texturemips=_convert_driver_project_png_to_texturemips,
        neutralize_helmet_mask=_neutralize_driver_helmet_mask_texturemips,
    )
    profile_layer_repair = _repair_driver_profile_layer_materials(profile, game_root)

    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_id = str(entry.get("skin_id") or "").strip()
    car_content_path = str(entry.get("car_content_path") or "").strip() or (f"content\\cars\\{entry.get('car_model')}" if entry.get("car_model") else "")
    visual_rel = normalize_skin_asset_rel_path(str(files.get("visual_preset") or ""))
    design_rel = normalize_skin_asset_rel_path(str(files.get("design") or ""))
    if not skin_id or not car_content_path or not visual_rel:
        raise ValueError("Skin is missing driver sync metadata")

    visual_path = game_root / Path(*split_content_path(visual_rel))
    design_path = game_root / Path(*split_content_path(design_rel)) if design_rel else None
    if not visual_path.exists():
        raise FileNotFoundError(f"Visual preset for driver sync not found: {visual_rel}")

    copied: list[str] = []
    patched: list[str] = []
    cleaned: list[str] = []
    local_synced: list[str] = []
    warnings: list[str] = []
    replacements: dict[str, str] = {}
    copy_targets: dict[str, tuple[Path, Path, str]] = {}
    local_root = _entry_local_root(entry)
    helmet_splatmap_targets_by_material: dict[str, str] = {}

    def add_replacement(source_ref: str, target_ref: str) -> None:
        source_norm = driver_skin.normalize_game_rel_path(source_ref)
        target_norm = driver_skin.normalize_game_rel_path(target_ref)
        if source_norm and target_norm and source_norm.lower() != target_norm.lower():
            replacements[_normalize_exact_text(source_norm)] = target_norm

    def game_asset_path(ref: str) -> Path:
        return game_root / Path(*split_content_path(driver_skin.normalize_game_rel_path(ref)))

    def add_copy_path(source: Path, source_ref: str, target_ref: str, label: str, missing_label: str = "Driver profile asset") -> Path | None:
        source_norm = driver_skin.normalize_game_rel_path(source_ref)
        target_norm = driver_skin.normalize_game_rel_path(target_ref)
        if not source_norm or not target_norm:
            return None
        existing = copy_targets.get(target_norm.lower())
        if existing:
            return existing[0]
        if not source.exists():
            warnings.append(f"{missing_label} missing: {source_norm}")
            return None
        target = game_root / Path(*split_content_path(target_norm))
        copy_targets[target_norm.lower()] = (source, target, label)
        return source

    def add_copy(source_ref: str, target_ref: str, label: str) -> Path | None:
        source_norm = driver_skin.normalize_game_rel_path(source_ref)
        return add_copy_path(_driver_profile_asset_path(profile, source_norm), source_norm, target_ref, label)

    def add_game_copy(source_ref: str, target_ref: str, label: str) -> Path | None:
        source_norm = driver_skin.normalize_game_rel_path(source_ref)
        return add_copy_path(game_asset_path(source_norm), source_norm, target_ref, label, missing_label="Game driver asset")

    def helmet_variant_texture_ref(material_info: dict[str, Any], suffix: str) -> str:
        suffix_clean = str(suffix or "").strip().lstrip("_")
        if not suffix_clean:
            return ""
        for slot in material_info.get("textureSlots") or []:
            texture_ref = driver_skin.normalize_game_rel_path(str(slot.get("value") or ""))
            lower = texture_ref.lower()
            if "\\1_helmets\\" not in lower or "\\textures\\" not in lower:
                continue
            name = Path(texture_ref.replace("\\", "/")).stem
            match = re.match(r"(helmet_\d+)_", name, re.I)
            if not match:
                continue
            folder = texture_ref.rsplit("\\", 1)[0]
            return driver_skin.normalize_game_rel_path(f"{folder}\\{match.group(1)}_{suffix_clean}.texture")
        return ""

    def add_helmet_neutral_mask(material_ref: str, target_material_ref: str, material_info: dict[str, Any]) -> None:
        mask_ref = helmet_variant_texture_ref(material_info, "D")
        if not mask_ref:
            return
        target_mask_ref = driver_skin.target_ref_for_driver_asset(mask_ref, car_content_path, skin_id, source_material_ref=material_ref)
        add_replacement(mask_ref, target_mask_ref)
        profile_mask_source = _driver_profile_asset_path(profile, mask_ref)
        texture_source = (
            add_copy_path(profile_mask_source, mask_ref, target_mask_ref, "driver profile neutral mask texture")
            if profile_mask_source.exists()
            else None
        )
        texture_source_from_game = False
        if texture_source is None:
            texture_source = add_game_copy(mask_ref, target_mask_ref, "driver profile neutral mask texture")
            texture_source_from_game = bool(texture_source)
        if texture_source:
            mask_mips_ref = driver_skin.normalize_game_rel_path(str(Path(mask_ref).with_suffix(".texturemips")))
            target_mask_mips_ref = driver_skin.normalize_game_rel_path(str(Path(target_mask_ref).with_suffix(".texturemips")))
            mask_mips_source = game_asset_path(mask_mips_ref) if texture_source_from_game else _driver_profile_asset_path(profile, mask_mips_ref)
            if mask_mips_source.exists():
                add_copy_path(mask_mips_source, mask_mips_ref, target_mask_mips_ref, "driver profile neutral mask texturemips")
        helmet_splatmap_targets_by_material[target_material_ref.lower()] = target_mask_ref

    def game_character_ref_for_local_driver_ref(ref: str) -> str:
        normalized = driver_skin.normalize_game_rel_path(ref)
        marker = "\\driver\\characters\\"
        lower = normalized.lower()
        index = lower.find(marker)
        if index < 0:
            return ""
        return driver_skin.normalize_game_rel_path(f"content\\characters\\{normalized[index + len(marker):]}")

    def add_material_tree(material_ref: str, design_slot: bool = False, source_from_game: bool = False) -> None:
        target_material_ref = (
            driver_skin.target_ref_for_design_driver_material(material_ref, car_content_path, skin_id)
            if design_slot
            else driver_skin.target_ref_for_driver_asset(material_ref, car_content_path, skin_id)
        )
        add_replacement(material_ref, target_material_ref)
        material_source = (
            add_game_copy(material_ref, target_material_ref, "driver profile material")
            if source_from_game
            else add_copy(material_ref, target_material_ref, "driver profile material")
        )
        if not material_source:
            return
        try:
            material_info = driver_skin.inspect_driver_material_data(material_source.read_bytes())
        except Exception:
            material_info = {}
        if driver_skin.classify_driver_ref(material_ref) == "helmet":
            add_helmet_neutral_mask(material_ref, target_material_ref, material_info)
        for slot in material_info.get("textureSlots", []):
            texture_ref = driver_skin.normalize_game_rel_path(str(slot.get("value") or ""))
            if not texture_ref or not driver_skin._is_localizable_driver_asset_ref(texture_ref, car_content_path):
                continue
            target_texture_ref = driver_skin.target_ref_for_driver_asset(texture_ref, car_content_path, skin_id, source_material_ref=material_ref)
            add_replacement(texture_ref, target_texture_ref)
            profile_texture_source = _driver_profile_asset_path(profile, texture_ref)
            texture_source = None
            texture_source_from_game = source_from_game
            if source_from_game and profile_texture_source.exists():
                texture_source = add_copy_path(profile_texture_source, texture_ref, target_texture_ref, "driver profile texture")
                texture_source_from_game = False
            if texture_source is None:
                texture_source = (
                    add_game_copy(texture_ref, target_texture_ref, "driver profile texture")
                    if source_from_game
                    else add_copy(texture_ref, target_texture_ref, "driver profile texture")
                )
            if texture_source:
                mips_source_ref = driver_skin.normalize_game_rel_path(str(Path(texture_ref).with_suffix(".texturemips")))
                mips_target_ref = driver_skin.normalize_game_rel_path(str(Path(target_texture_ref).with_suffix(".texturemips")))
                mips_source = game_asset_path(mips_source_ref) if texture_source_from_game else _driver_profile_asset_path(profile, mips_source_ref)
                if mips_source.exists():
                    add_copy_path(mips_source, mips_source_ref, mips_target_ref, "driver profile texturemips")

    def legacy_root_mirror_for(target: Path) -> Path | None:
        suffix = target.suffix.lower()
        if suffix not in {".driverfinalstate", ".material"}:
            return None
        skin_root = game_root / Path(*split_content_path(f"{car_content_path}\\skins\\{skin_id}"))
        driver_root = skin_root / "driver"
        try:
            target.resolve().relative_to(driver_root.resolve())
        except Exception:
            return None
        mirror = skin_root / target.name
        if not mirror.exists() or not mirror.is_file():
            return None
        if mirror.resolve() == target.resolve():
            return None
        return mirror

    def local_skin_mirror_for(target: Path) -> Path | None:
        if local_root is None:
            return None
        try:
            rel = target.resolve().relative_to(game_root.resolve())
        except Exception:
            return None
        mirror = local_root / rel
        try:
            if mirror.resolve() == target.resolve():
                return None
        except Exception:
            pass
        return mirror

    def sync_local_skin_mirror(source: Path, label: str) -> None:
        if not source.exists() or not source.is_file():
            return
        mirror = local_skin_mirror_for(source)
        if mirror is None:
            return
        before = mirror.read_bytes() if mirror.exists() and mirror.is_file() else None
        current = source.read_bytes()
        if before == current:
            return
        writer.copy_file(source, mirror, label=label)
        local_synced.append(str(mirror))

    def enforce_driverfinalstate_mesh_policy(target: Path) -> None:
        if not target.exists() or target.suffix.lower() != ".driverfinalstate":
            return
        raw = target.read_bytes()
        try:
            material_refs = driver_skin.extract_driverfinalstate_material_refs(raw)
            mesh_refs = driver_skin.extract_driverfinalstate_mesh_refs(raw)
        except Exception:
            return
        try:
            fields = parse_fields(raw)
        except Exception:
            fields = []
        if fields:
            kept_fields: list[ProtoField] = []
            removed_aux_helmet = False
            for field in fields:
                if field.number == 10 and field.wire == 2 and isinstance(field.value, bytes):
                    try:
                        nested = parse_fields(bytes(field.value))
                    except Exception:
                        nested = []
                    mesh_ref = ""
                    for nested_field in nested:
                        if nested_field.number == 1 and nested_field.wire == 2 and isinstance(nested_field.value, bytes):
                            mesh_ref = driver_skin.normalize_game_rel_path(is_printable_utf8(nested_field.value) or "")
                            break
                    if mesh_ref and driver_skin.classify_driver_ref(mesh_ref) == "helmet":
                        removed_aux_helmet = True
                        continue
                kept_fields.append(field)
            if removed_aux_helmet:
                new_raw = build_fields(kept_fields)
                _write_game_bytes(target, new_raw, writer, "driver profile auxiliary helmet mesh cleanup", backup=True)
                patched.append(str(target))
                raw = new_raw
                try:
                    material_refs = driver_skin.extract_driverfinalstate_material_refs(raw)
                    mesh_refs = driver_skin.extract_driverfinalstate_mesh_refs(raw)
                except Exception:
                    return
        exact_replacements: dict[str, str] = {}
        for mesh_ref in mesh_refs:
            mesh_category = driver_skin.classify_driver_ref(mesh_ref)
            if mesh_category not in driver_skin.DRIVER_VISUAL_MESH_CATEGORIES:
                continue
            if driver_skin.source_driver_mesh_should_localize(mesh_ref, material_refs):
                continue
            original_mesh_ref = game_character_ref_for_local_driver_ref(mesh_ref)
            if original_mesh_ref:
                exact_replacements[_normalize_exact_text(driver_skin.normalize_game_rel_path(mesh_ref))] = original_mesh_ref
        if not exact_replacements:
            return
        new_raw = patch_strings_recursive_exact(raw, exact_replacements)
        if new_raw == raw:
            return
        _write_game_bytes(target, new_raw, writer, "driver profile source mesh policy", backup=True)
        patched.append(str(target))

    def cleanup_stale_helmet_character_assets() -> None:
        helmet_root = game_root / Path(*split_content_path(f"{car_content_path}\\skins\\{skin_id}\\driver\\characters\\1_helmets"))
        if not helmet_root.exists() or not helmet_root.is_dir():
            return
        active_targets = {str(target.resolve()).lower() for _source, target, _label in copy_targets.values() if target.exists()}
        stale_candidates: list[Path] = []
        for path in sorted((item for item in helmet_root.rglob("*") if item.is_file()), key=lambda item: str(item).lower()):
            try:
                rel = driver_skin.normalize_game_rel_path(str(path.relative_to(helmet_root)))
            except Exception:
                continue
            rel_lower = rel.lower()
            should_cleanup = (
                "\\materials\\" in f"\\{rel_lower}"
                or rel_lower.startswith("helmet_3\\textures\\")
                or rel_lower.startswith("helmet_3\\meshes\\")
            )
            if not should_cleanup:
                continue
            if str(path.resolve()).lower() in active_targets:
                continue
            stale_candidates.append(path)
        for path in stale_candidates:
            writer.delete_file(path, label="stale driver helmet character asset", backup=True)
            cleaned.append(str(path))
            mirror = local_skin_mirror_for(path)
            if mirror and mirror.exists() and mirror.is_file():
                try:
                    same_file = mirror.resolve() == path.resolve()
                except Exception:
                    same_file = str(mirror).lower() == str(path).lower()
                if not same_file:
                    writer.delete_file(mirror, label="stale driver helmet local mirror", backup=True)
                    cleaned.append(str(mirror))

    driver_ref = driver_skin.normalize_game_rel_path(str(profile.get("localDriverFinalState") or profile.get("driverFinalState") or ""))
    if not driver_ref:
        source_visual = _driver_profile_asset_path(profile, str(profile.get("sourceVisualPreset") or ""))
        if source_visual.exists():
            driver_ref = driver_skin.extract_visual_driverfinalstate_ref(source_visual.read_bytes())
    if not driver_ref:
        raise ValueError(f"Driver profile has no driver final state: {profile_id}")

    target_driver_ref = driver_skin.target_ref_for_driver_asset(driver_ref, car_content_path, skin_id)
    add_replacement(driver_ref, target_driver_ref)
    driver_source = add_copy(driver_ref, target_driver_ref, "driver profile final state")
    if driver_source:
        driver_source_data = driver_source.read_bytes()
        driver_material_refs = driver_skin.extract_driverfinalstate_material_refs(driver_source_data)
        target_material_by_category: dict[str, str] = {}
        for material_ref in driver_material_refs:
            target_material_ref = driver_skin.target_ref_for_driver_asset(material_ref, car_content_path, skin_id)
            category = driver_skin.classify_driver_ref(material_ref)
            if category in driver_skin.DRIVER_VISUAL_MATERIAL_CATEGORIES and category not in target_material_by_category:
                target_material_by_category[category] = target_material_ref
            add_material_tree(material_ref, design_slot=False)
        for mesh_ref in driver_skin.extract_driverfinalstate_mesh_refs(driver_source_data):
            mesh_category = driver_skin.classify_driver_ref(mesh_ref)
            if not driver_skin.source_driver_mesh_should_localize(mesh_ref, driver_material_refs):
                original_mesh_ref = game_character_ref_for_local_driver_ref(mesh_ref) if mesh_category == "helmet" else ""
                if original_mesh_ref:
                    add_replacement(mesh_ref, original_mesh_ref)
                continue
            target_mesh_ref = driver_skin.target_ref_for_driver_asset(mesh_ref, car_content_path, skin_id)
            add_replacement(mesh_ref, target_mesh_ref)
            profile_mesh_source = _driver_profile_asset_path(profile, driver_skin.normalize_game_rel_path(mesh_ref))
            game_mesh_ref = game_character_ref_for_local_driver_ref(mesh_ref) if mesh_category == "helmet" else ""
            game_mesh_source = game_asset_path(game_mesh_ref) if game_mesh_ref else Path()
            if mesh_category == "helmet" and game_mesh_ref and game_mesh_source.exists():
                mesh_source = add_game_copy(game_mesh_ref, target_mesh_ref, f"driver profile {mesh_category} mesh")
                mesh_source_from_game = bool(mesh_source)
            elif profile_mesh_source.exists():
                mesh_source = add_copy_path(profile_mesh_source, mesh_ref, target_mesh_ref, f"driver profile {mesh_category} mesh")
                mesh_source_from_game = False
            else:
                mesh_source = add_game_copy(mesh_ref, target_mesh_ref, f"driver profile {mesh_category} mesh")
                mesh_source_from_game = bool(mesh_source)
            if not mesh_source:
                continue
            try:
                mesh_material_refs = driver_skin.extract_mesh_material_refs(mesh_source.read_bytes())
            except Exception:
                mesh_material_refs = []
            for material_ref in mesh_material_refs:
                material_category = driver_skin.classify_driver_ref(material_ref)
                if material_category not in driver_skin.DRIVER_VISUAL_MATERIAL_CATEGORIES:
                    continue
                source_material_ref = driver_skin.source_driver_material_for_mesh_material(mesh_ref, material_ref, driver_material_refs)
                if source_material_ref:
                    add_replacement(material_ref, driver_skin.target_ref_for_driver_asset(source_material_ref, car_content_path, skin_id))
                    continue
                add_material_tree(material_ref, design_slot=False, source_from_game=mesh_source_from_game)

    design_candidates = [
        rel
        for rel in (profile.get("files") or [])
        if str(rel or "").strip().lower().endswith(".design")
    ]
    for design_source_rel in design_candidates:
        design_source = _driver_profile_asset_path(profile, str(design_source_rel))
        if not design_source.exists():
            continue
        try:
            design_slots = driver_skin.extract_design_driver_slots(design_source.read_bytes())
        except Exception:
            design_slots = {}
        for material_refs in design_slots.values():
            for material_ref in material_refs:
                add_material_tree(material_ref, design_slot=True)

    for source, target, label in copy_targets.values():
        before = target.read_bytes() if target.exists() and target.is_file() else None
        writer.copy_file(source, target, label=label)
        if before != target.read_bytes():
            copied.append(str(target))

    replacement_map = {_normalize_exact_text(old): new for old, new in replacements.items()}
    for _source, target, _label in copy_targets.values():
        if not target.exists() or target.suffix.lower() not in driver_skin.PATCHABLE_SUFFIXES:
            continue
        raw = target.read_bytes()
        new_raw = patch_strings_recursive_exact(raw, replacement_map)
        if new_raw != raw:
            _write_game_bytes(target, new_raw, writer, "driver profile reference patch", backup=True)
            patched.append(str(target))
        enforce_driverfinalstate_mesh_policy(target)

    for _source, target, _label in copy_targets.values():
        if not target.exists() or target.suffix.lower() != ".material":
            continue
        try:
            rel = driver_skin.normalize_game_rel_path(str(target.resolve().relative_to(game_root.resolve())))
        except Exception:
            rel = driver_skin.normalize_game_rel_path(str(target))
        if driver_skin.classify_driver_ref(rel) not in driver_projects.DRIVER_MATERIAL_SOURCE_CATEGORIES:
            continue
        material_category = driver_skin.classify_driver_ref(rel)
        neutral_parameters = driver_skin.png_only_neutral_parameters_for_category(material_category)
        texture_replacements = driver_skin.png_only_texture_replacements_for_category(material_category)
        forced_texture_slots = dict(driver_skin.png_only_texture_slots_for_category(material_category))
        if material_category == "helmet":
            splatmap_ref = helmet_splatmap_targets_by_material.get(rel.lower(), "")
            if splatmap_ref:
                forced_texture_slots["SpatMap"] = splatmap_ref
        if not neutral_parameters:
            continue
        raw = target.read_bytes()
        new_raw = driver_skin.set_material_parameters_data(raw, neutral_parameters)
        if texture_replacements:
            new_raw = patch_strings_recursive_exact(
                new_raw,
                {_normalize_exact_text(old): new for old, new in texture_replacements.items()},
            )
        if forced_texture_slots:
            new_raw = _set_material_texture_slots_nested(new_raw, forced_texture_slots)
        new_raw = _ensure_material_override_flags(new_raw, 21, set(neutral_parameters), 1)
        texture_override_slots = set(forced_texture_slots)
        if material_category == "helmet":
            texture_override_slots.add("Base_BaseColorMap")
        if texture_override_slots:
            new_raw = _ensure_material_override_flags(new_raw, 22, texture_override_slots, 1)
        if new_raw != raw:
            _write_game_bytes(target, new_raw, writer, "driver profile png-only material", backup=True)
            patched.append(str(target))

    neutralized_masks: set[str] = set()
    for _source, target, _label in copy_targets.values():
        if not target.exists() or not _is_driver_mask_texture_path(target):
            continue
        key = str(target.resolve()).lower()
        if key in neutralized_masks:
            continue
        neutralized_masks.add(key)
        result = _neutralize_driver_helmet_mask_texturemips(target, writer=writer)
        if result.get("updated"):
            patched.append(str(result.get("texturemips") or target.with_suffix(".texturemips")))

    cleanup_stale_helmet_character_assets()

    for _source, target, _label in copy_targets.values():
        mirror = legacy_root_mirror_for(target)
        if not mirror:
            continue
        before = mirror.read_bytes()
        writer.copy_file(target, mirror, label="driver profile legacy root mirror")
        if before != mirror.read_bytes():
            copied.append(str(mirror))

    for _source, target, _label in copy_targets.values():
        sync_local_skin_mirror(target, "driver profile local mirror")
        if _is_driver_mask_texture_path(target):
            sync_local_skin_mirror(target.with_suffix(".texturemips"), "driver profile local mask mirror")

    raw_visual = visual_path.read_bytes()
    new_visual = driver_skin.patch_visual_driverfinalstate_ref(raw_visual, target_driver_ref)
    if new_visual != raw_visual:
        _write_game_bytes(visual_path, new_visual, writer, "visual driver profile", backup=True)
        patched.append(str(visual_path))
    sync_local_skin_mirror(visual_path, "driver profile local visual mirror")

    if design_path and design_path.exists() and replacement_map:
        raw_design = design_path.read_bytes()
        new_design = patch_strings_recursive_exact(raw_design, replacement_map)
        if new_design != raw_design:
            _write_game_bytes(design_path, new_design, writer, "design driver profile", backup=True)
            patched.append(str(design_path))
    if design_path and design_path.exists():
        sync_local_skin_mirror(design_path, "driver profile local design mirror")

    selection["appliedAt"] = datetime.now().isoformat(timespec="seconds")
    selection["appliedDriverFinalState"] = target_driver_ref
    entry["driver_profile"] = _json_ready(selection)
    return {
        "changed": bool(copied or patched or cleaned or local_synced or profile_layer_repair.get("changed")),
        "profileId": profile_id,
        "profileName": str(profile.get("name") or profile_id),
        "copied": copied,
        "patched": patched,
        "cleaned": cleaned,
        "localSynced": local_synced,
        "copiedCount": len(copied),
        "patchedCount": len(patched),
        "cleanedCount": len(cleaned),
        "localSyncedCount": len(local_synced),
        "driverFinalState": target_driver_ref,
        "projectSync": project_sync,
        "profileLayerRepair": profile_layer_repair,
        "warnings": warnings,
    }


def _sync_livery_project_textures_to_local_skin(entry: dict[str, Any], game_root: Path) -> dict[str, Any]:
    local_root = _entry_local_root(entry)
    if local_root is None:
        return {"updated": 0, "skipped": 0, "missing": 0, "items": [], "writer": {}}
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_folder = _local_skin_asset_path(entry, str(files.get("skin_folder") or ""))
    if skin_folder is None or not skin_folder.exists():
        return {"updated": 0, "skipped": 0, "missing": 1, "items": [{"label": "skin folder", "status": "missing"}], "writer": {}}

    prepared = prepare_livery_preview_project(str(entry.get("key") or ""), game_root)
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    specs = _livery_project_texture_specs(game_root, entry, project, skin_folder, local_only=True, include_banner=False)
    items: list[dict[str, Any]] = []
    candidates: list[dict[str, Any]] = []
    for spec in specs:
        label = str(spec.get("label") or "")
        png = Path(str(spec.get("png") or ""))
        texture = (
            _find_banner_texture_for_skin(game_root, entry, skin_folder, local_only=True)
            if spec.get("banner")
            else Path(str(spec.get("texture") or "")) if str(spec.get("texture") or "").strip() else _find_texture_by_stem(skin_folder, str(spec.get("stem") or ""))
        )
        item: dict[str, Any] = {"label": label, "png": str(png), "texture": str(texture or ""), "status": "pending"}
        if not png.exists() or _is_preview_placeholder_png(png):
            item.update({"status": "skipped" if spec.get("optional") else "missing_png", "reason": "optional project PNG not found" if spec.get("optional") else "project PNG not found"})
            items.append(item)
            continue
        if texture is None or not texture.exists():
            item.update({"status": "skipped" if spec.get("optional") else "missing_texture", "reason": "optional local .texture not found" if spec.get("optional") else "local .texture not found"})
            items.append(item)
            continue
        mips = texture.with_suffix(".texturemips")
        if not mips.exists():
            item.update({"status": "skipped" if spec.get("optional") else "missing_mips", "reason": "optional local .texturemips not found" if spec.get("optional") else "local .texturemips not found"})
            items.append(item)
            continue
        item["texturemips"] = str(mips)
        candidates.append({"item": item, "png": png, "texture": texture, "mips": mips, "slot": str(spec.get("slot") or ""), "stem": str(spec.get("stem") or "")})

    ext_banner_item = _ensure_ext_banner_project_texture_for_skin(
        entry,
        game_root,
        local_root,
        skin_folder,
        project,
        writer=None,
        only_if_newer=False,
    )
    items.append(ext_banner_item)
    visual_rel = normalize_skin_asset_rel_path(str(files.get("visual_preset") or ""))
    local_ext_banner_sync = _ensure_ext_banner_for_game_entry(
        entry,
        local_root,
        visual_rel,
        [],
        None,
        include_savedcars=False,
    )
    items.append({
        "label": "EXT_BANNER local state",
        "status": "updated" if local_ext_banner_sync.get("changed") else ("skipped" if local_ext_banner_sync.get("skipped") else "current"),
        **local_ext_banner_sync,
    })

    if not candidates:
        return {
            **prepared,
            "updated": sum(1 for item in items if item.get("status") == "updated"),
            "skipped": sum(1 for item in items if item.get("status") == "skipped"),
            "missing": sum(1 for item in items if str(item.get("status", "")).startswith("missing")),
            "items": items,
            "writer": {},
        }

    texconv = find_texconv_executable()
    if texconv is None:
        raise RuntimeError("texconv.exe was not found. Place it in LiveryLab/tools or configure it in Settings.")

    local_write_results: list[dict[str, Any]] = []
    local_write_plan: list[dict[str, Any]] = []
    tmp_dir = create_short_work_temp_dir("su")
    try:
        for index, row in enumerate(candidates, start=1):
            item = row["item"]
            png = row["png"]
            texture = row["texture"]
            mips = row["mips"]
            desc = parse_texture_descriptor(texture)
            format_name, auto_note = choose_auto_format(texture, desc, len(mips.read_bytes()), str(row.get("slot") or ""))
            dds_path = tmp_dir / f"t{index}.dds"
            label = str(item.get("label") or "texture")
            repair = _repair_neutral_project_png_size(png, texture, str(row.get("stem") or ""))
            if repair.get("repaired"):
                item["sizeRepair"] = repair
            validate_project_png_size_for_texture(png, desc, label)
            convert_image_to_dds_with_texconv(png, dds_path, format_name, texconv, int(desc["mips"]))
            texturemips_data = texturemips_bytes_from_dds(texture, dds_path)
            local_write_plan.append({
                "action": "write_bytes",
                "target": str(mips),
                "source": str(png),
                "label": label,
                "backup": True,
            })
            backup = safe_write_bytes(mips, texturemips_data)
            write_result = {
                "action": "write_bytes",
                "target": str(mips),
                "source": str(png),
                "label": label,
                "status": "written",
                "backup": str(backup) if backup else "",
                "bytes_written": len(texturemips_data),
            }
            local_write_results.append(write_result)
            item.update({
                "status": "updated",
                "format": format_name,
                "note": auto_note,
                "mips": int(desc["mips"]),
                "width": int(desc["width"]),
                "height": int(desc["height"]),
                "bytes": len(texturemips_data),
                "writeResult": write_result,
            })
            items.append(item)
    finally:
        cleanup_short_work_temp_dir(tmp_dir)

    return {
        **prepared,
        "updated": sum(1 for item in items if item.get("status") == "updated"),
        "skipped": sum(1 for item in items if item.get("status") == "skipped"),
        "missing": sum(1 for item in items if str(item.get("status", "")).startswith("missing")),
        "items": items,
        "writer": {
            "operation": "update local skin textures from LiveryProjects",
            "root": str(local_root),
            "preflight": {
                "operation": "update local skin textures from LiveryProjects",
                "root": str(local_root),
                "writeReady": True,
                "summary": "Local skin write ready",
                "warnings": [],
                "targets": [],
                "checks": [],
            },
            "plan": local_write_plan,
            "results": local_write_results,
        },
    }


def _png_bytes_for_skin_thumbnail(source: str | Path | None = None, allow_resize: bool = False) -> bytes:
    try:
        from PIL import Image, ImageOps
    except Exception as exc:
        raise RuntimeError(f"Pillow is required for livery thumbnail handling: {exc}") from exc

    image = None
    try:
        if source is None:
            image = Image.new("RGBA", (1024, 1024), (18, 23, 29, 255))
        else:
            image_path = Path(source)
            if not image_path.exists():
                raise FileNotFoundError(f"Preview image not found: {image_path}")
            image = Image.open(image_path)
        if image.size != (1024, 1024):
            if not allow_resize:
                raise RuntimeError(f"Preview image must be 1024x1024, got {image.size[0]}x{image.size[1]}")
            image = ImageOps.fit(image.convert("RGBA"), (1024, 1024), method=Image.Resampling.LANCZOS)
        else:
            image = image.convert("RGBA")
        with io.BytesIO() as buffer:
            image.save(buffer, format="PNG")
            return buffer.getvalue()
    finally:
        if image is not None:
            try:
                image.close()
            except Exception:
                pass


def _write_skin_thumbnail_png(
    source: str | Path | None,
    target: str | Path,
    *,
    allow_resize: bool = False,
    writer: GameFileWriter | None = None,
    label: str = "skin thumbnail png",
    backup: bool = True,
) -> bool:
    target_path = Path(target)
    data = _png_bytes_for_skin_thumbnail(source, allow_resize=allow_resize)
    if target_path.exists():
        try:
            if target_path.read_bytes() == data:
                return False
        except Exception:
            pass
    _write_game_bytes(target_path, data, writer, label=label, backup=backup)
    return True


def _project_skin_thumbnail_path_for_entry(entry: dict[str, Any]) -> Path:
    if skin_policy.policy_for_skin(entry).get("readOnly"):
        workflow = "Official" if _is_official_external_my_skin_entry(entry) else "Advanced"
        car_model = preview3d.safe_segment(entry.get("car_model"), "car")
        skin_key = preview3d.safe_segment(entry.get("key") or entry.get("skin_id") or entry.get("display_name"), "skin")
        return get_read_only_livery_preview_root() / "Thumbnails" / workflow / car_model / skin_key / SKIN_THUMBNAIL_FILENAME
    return _livery_project_folder_for_entry(entry) / SKIN_THUMBNAIL_FILENAME


def _source_skin_generated_thumbnail_for_entry(entry: dict[str, Any]) -> Path | None:
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    if not source_skin:
        return None
    game_root_text = str(entry.get("game_root") or "").strip()
    if not game_root_text:
        return None
    try:
        game_root = normalize_game_root(game_root_text)
    except Exception:
        return None
    car_content_path = str(source_skin.get("car_content_path") or entry.get("car_content_path") or "").strip()
    visual_rel = normalize_game_rel_path(str(source_skin.get("visual_preset") or ""))
    mechanical_rel = normalize_game_rel_path(str(source_skin.get("mechanical_preset") or ""))

    if (not mechanical_rel or not visual_rel):
        finalstate_path = _disk_from_game_rel(game_root, str(source_skin.get("carfinalstate") or ""))
        if finalstate_path and finalstate_path.exists():
            try:
                final_info = extract_carfinalstate_info(finalstate_path.read_bytes())
                mechanical_rel = mechanical_rel or normalize_game_rel_path(str(final_info.get("mechanical_preset") or ""))
                visual_rel = visual_rel or normalize_game_rel_path(str(final_info.get("visual_preset") or ""))
            except Exception:
                pass

    if not mechanical_rel and visual_rel:
        visual_path = _disk_from_game_rel(game_root, visual_rel)
        actor_path = _disk_from_game_rel(game_root, str(source_skin.get("actor") or ""))
        if visual_path and visual_path.exists():
            try:
                mechanical_path = find_mechanicalpreset_for_visual(
                    game_root,
                    actor_path if actor_path and actor_path.exists() else None,
                    visual_path,
                    visual_rel,
                )
                if mechanical_path and mechanical_path.exists():
                    mechanical_rel = normalize_game_rel_path(str(content_rel_from_disk(game_root, mechanical_path) or ""))
            except Exception:
                pass

    if not car_content_path or not mechanical_rel or not visual_rel:
        return None
    source_texture = generated_thumbnail_disk_path(game_root, car_content_path, mechanical_rel, visual_rel)
    if source_texture.exists() and source_texture.with_suffix(".texturemips").exists():
        return source_texture
    return None


def _import_source_skin_thumbnail_png(entry: dict[str, Any], target: Path, overwrite: bool = False) -> dict[str, Any] | None:
    source_texture = _source_skin_generated_thumbnail_for_entry(entry)
    if source_texture is None:
        return None
    converted = convert_texture_to_png(source_texture, target, overwrite=overwrite, create_dds=False)
    return {
        "path": str(target),
        "status": "source_game_thumbnail" if not converted.get("skipped") else "current",
        "changed": not bool(converted.get("skipped")),
        "source": str(source_texture),
        "reason": str(converted.get("reason") or ""),
    }


def _ensure_project_skin_thumbnail_png(entry: dict[str, Any], preview_image: str | Path | None = None) -> dict[str, Any]:
    project_path = _project_skin_thumbnail_path_for_entry(entry)
    project_path.parent.mkdir(parents=True, exist_ok=True)
    if preview_image:
        changed = _write_skin_thumbnail_png(
            preview_image,
            project_path,
            allow_resize=False,
            label="project skin thumbnail png",
            backup=True,
        )
        return {"path": str(project_path), "status": "updated" if changed else "current", "changed": changed, "source": str(preview_image)}

    if project_path.exists():
        if not _is_preview_placeholder_png(project_path):
            return {"path": str(project_path), "status": "existing", "changed": False, "source": ""}
        placeholder_data = project_path.read_bytes()
        try:
            project_path.unlink(missing_ok=True)
            imported = _import_source_skin_thumbnail_png(entry, project_path, overwrite=False)
            if imported:
                imported["replacedPlaceholder"] = True
                return imported
        except Exception as exc:
            if not project_path.exists():
                project_path.write_bytes(placeholder_data)
            return {"path": str(project_path), "status": "existing_placeholder", "changed": False, "source": "", "warning": str(exc)}
        if not project_path.exists():
            project_path.write_bytes(placeholder_data)
        return {"path": str(project_path), "status": "existing_placeholder", "changed": False, "source": ""}

    try:
        imported = _import_source_skin_thumbnail_png(entry, project_path, overwrite=False)
        if imported:
            return imported
    except Exception as exc:
        source_warning = str(exc)
    else:
        source_warning = ""

    placeholder = _web_asset_path("placeholder.png")
    changed = _write_skin_thumbnail_png(
        placeholder if placeholder and placeholder.exists() else None,
        project_path,
        allow_resize=True,
        label="project skin thumbnail placeholder",
        backup=False,
    )
    return {
        "path": str(project_path),
        "status": "placeholder" if changed else "current",
        "changed": changed,
        "source": str(placeholder) if placeholder else "embedded",
        "warning": source_warning,
    }


def sync_livery_project_thumbnail_to_local_skin(
    entry: dict[str, Any],
    preview_image: str | Path | None = None,
    writer: GameFileWriter | None = None,
    promote_existing_local: bool = True,
) -> dict[str, Any]:
    local_root = _entry_local_root(entry)
    read_only = bool(skin_policy.policy_for_skin(entry).get("readOnly"))
    if local_root is None and not read_only:
        return {"changed": False, "project": {}, "local": {}, "reason": "local root missing"}
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    official_project = bool(
        _is_official_external_my_skin_entry(entry)
        and official_external_workflow.role_for_entry(entry) == official_external_workflow.ROLE_PROJECT
    )
    if official_project:
        if local_root is None:
            return {"changed": False, "project": {}, "local": {}, "reason": "official project local root missing"}
        project = _ensure_project_skin_thumbnail_png(entry, preview_image=preview_image)
        project_path = Path(str(project.get("path") or ""))
        if not project_path.exists() or not project_path.is_file():
            return {
                "changed": bool(project.get("changed")),
                "project": project,
                "local": {},
                "reason": "official project thumbnail missing",
            }
        try:
            thumbnail_ref = norm_slashes(str(project_path.resolve().relative_to(local_root.resolve())))
        except Exception:
            thumbnail_ref = project_path.name
        files["thumbnail_png"] = thumbnail_ref
        entry["files"] = files
        return {
            "changed": bool(project.get("changed")),
            "project": project,
            "local": {
                "path": str(project_path),
                "status": "synced" if project.get("changed") else "current",
                "changed": bool(project.get("changed")),
            },
            "layout": "official_project_root",
        }

    skin_folder_rel = str(files.get("skin_folder") or "").strip()
    stored_local_thumbnail = _skin_thumbnail_png_disk_path(local_root, skin_folder_rel) if local_root is not None else None
    local_target = None if read_only else stored_local_thumbnail
    if local_target is None and not read_only:
        return {"changed": False, "project": {}, "local": {}, "reason": "skin folder missing"}

    project_path = _project_skin_thumbnail_path_for_entry(entry)
    project_preview = preview_image
    if promote_existing_local and not project_preview and not project_path.exists() and stored_local_thumbnail is not None and stored_local_thumbnail.exists():
        project_preview = stored_local_thumbnail
    project = _ensure_project_skin_thumbnail_png(entry, preview_image=project_preview)
    project_path = Path(str(project.get("path") or ""))
    if not project_path.exists():
        return {"changed": bool(project.get("changed")), "project": project, "local": {}, "reason": "project thumbnail missing"}
    if local_target is None or local_root is None:
        return {
            "changed": bool(project.get("changed")),
            "project": project,
            "local": {},
            "reason": "read-only preview stored in LiveryLab cache",
            "layout": "read_only_preview_cache",
        }

    local_changed = _write_skin_thumbnail_png(
        project_path,
        local_target,
        allow_resize=False,
        writer=writer,
        label="local skin thumbnail png",
        backup=True,
    )
    files["thumbnail_png"] = game_rel_from_disk(local_root, local_target)
    entry["files"] = files
    return {
        "changed": bool(project.get("changed") or local_changed),
        "project": project,
        "local": {
            "path": str(local_target),
            "status": "synced" if local_changed else "current",
            "changed": local_changed,
        },
    }


def _activation_dependency_source_label(path: Path, local_root: Path | None) -> str:
    if local_root is not None:
        try:
            return norm_slashes(str(path.resolve().relative_to(local_root.resolve())))
        except Exception:
            pass
    return str(path)


def _activation_reference_scan_files(entry: dict[str, Any]) -> list[tuple[str, Path]]:
    scan_suffixes = {".cardesign", ".design", ".visualcarpreset", ".carfinalstate", ".material"}
    paths: list[tuple[str, Path]] = []
    seen: set[str] = set()

    def add(kind: str, path: Path | None) -> None:
        if not path or not path.exists() or not path.is_file():
            return
        if path.suffix.lower() not in scan_suffixes:
            return
        if "_acevo_backups" in {part.lower() for part in path.parts}:
            return
        key = str(path.resolve()).lower()
        if key in seen:
            return
        seen.add(key)
        paths.append((kind, path))

    for kind, path in _skin_file_paths_for_kinds(entry, {"cardesign", "design", "visual_preset", "carfinalstate", "carfinalstates"}):
        add(kind, path)

    for material_path in _local_livery_material_paths(entry):
        add("material", material_path)

    local_root = _entry_local_root(entry)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_folder = _local_skin_asset_path(entry, str(files.get("skin_folder") or ""))
    if local_root is not None and skin_folder and skin_folder.exists() and skin_folder.is_dir():
        for material_path in skin_folder.rglob("*.material"):
            add("material", material_path)

    return paths


def _missing_activation_content_references(entry: dict[str, Any], game_root: Path) -> list[dict[str, Any]]:
    local_root = _entry_local_root(entry)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_folder_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    missing: list[dict[str, Any]] = []
    seen_missing: set[str] = set()
    seen_refs: set[tuple[str, str]] = set()

    for kind, path in _activation_reference_scan_files(entry):
        try:
            strings = collect_proto_strings(path.read_bytes())
        except Exception:
            continue
        source_label = _activation_dependency_source_label(path, local_root)
        for _proto_path, text in strings:
            for _start, ref in _extract_content_references(text):
                ref_norm = normalize_skin_asset_rel_path(ref)
                if not ref_norm:
                    continue
                ref_key = ref_norm.lower()
                source_ref_key = (source_label.lower(), ref_key)
                if source_ref_key in seen_refs:
                    continue
                seen_refs.add(source_ref_key)

                local_exists = False
                if local_root is not None:
                    local_exists = (local_root / Path(*split_content_path(ref_norm))).exists()
                game_exists = resolve_content_path(game_root, ref_norm).exists()
                if local_exists or game_exists:
                    continue
                if skin_folder_rel and not ref_norm.lower().startswith(skin_folder_rel.lower() + "\\"):
                    continue
                if ref_key in seen_missing:
                    continue
                seen_missing.add(ref_key)
                missing.append({
                    "kind": "contentReference",
                    "path": ref_norm,
                    "sourceKind": kind,
                    "source": source_label,
                    "message": f"Referenced content file missing: {ref_norm}",
                })
    return missing


def inspect_my_skin_activation_dependencies(entry: dict[str, Any], game_root: Path | None = None) -> dict[str, Any]:
    root = game_root or normalize_game_root(str(entry.get("game_root") or ""))
    local_root = _entry_local_root(entry)
    critical: list[dict[str, Any]] = []
    warnings: list[dict[str, Any]] = []

    bake = entry.get("numberplate_bake") if isinstance(entry.get("numberplate_bake"), dict) else {}
    variant = entry.get("numberplate_variant") if isinstance(entry.get("numberplate_variant"), dict) else {}
    baked_paths = skin_package_dependencies.baked_numberplate_paths(bake)
    if baked_paths and local_root is not None:
        for row in baked_paths:
            if row.get("field") == "pngPath":
                continue
            rel = normalize_skin_asset_rel_path(str(row.get("path") or ""))
            if rel and not (local_root / Path(*split_content_path(rel))).exists():
                critical.append({
                    "kind": "numberplateBake",
                    "path": rel,
                    "message": f"Baked numberplate file missing: {rel}",
                })
    template_id = str(bake.get("template_id") or variant.get("template_id") or "").strip()
    if baked_paths and template_id and not _skin_numberplate_template_available(template_id):
        warnings.append({
            "kind": "numberplateTemplate",
            "id": template_id,
            "message": "Original numberplate template is not installed; baked numberplates can be activated but cannot be rebaked.",
        })
    if variant.get("source_type") == "template" and template_id and not baked_paths and not _skin_numberplate_template_available(template_id):
        critical.append({
            "kind": "numberplateTemplate",
            "id": template_id,
            "message": f"Numberplate template missing and no baked files are available: {template_id}",
        })

    driver_selection = _driver_profile_selection(entry)
    if driver_selection:
        profile_id = str(driver_selection.get("profileId") or driver_selection.get("id") or "").strip()
        car_model = str(driver_selection.get("carModel") or entry.get("car_model") or "").strip()
        if not _driver_profile_from_selection(driver_selection, car_model):
            critical.append({
                "kind": "driverProfile",
                "id": profile_id,
                "message": f"Driver profile missing: {profile_id}",
            })

    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    visual_path = _skin_file_disk_path(root, entry, str(files.get("visual_preset") or ""))
    if visual_path and visual_path.exists():
        for row in _visualpreset_tyretexture_refs(visual_path.read_bytes()):
            ref = normalize_game_rel_path(str(row.get("path") or ""))
            if not ref:
                continue
            if _custom_tyre_manifest_for_ref(ref):
                continue
            ref_lower = ref.lower()
            looks_custom = (
                "\\common_assets\\parts\\tyres\\textures\\racing_" in ref_lower
                and "racing_generic" not in ref_lower
            )
            if looks_custom and not resolve_content_path(root, ref).exists():
                critical.append({
                    "kind": "tyreBrand",
                    "path": ref,
                    "message": f"Custom tyre brand dependency missing: {ref}",
                })

    critical.extend(_missing_activation_content_references(entry, root))

    return {
        "critical": _json_ready(critical),
        "criticalCount": len(critical),
        "warnings": _json_ready(warnings),
        "warningCount": len(warnings),
    }


def _save_my_skin_entry(entry: dict[str, Any]) -> Path:
    key = str(entry.get("key") or "").lower()
    if not key:
        raise ValueError("My Skin entry key is missing")
    skins = load_my_skins()
    replaced = False
    for index, item in enumerate(skins):
        if str(item.get("key") or "").lower() == key:
            skins[index] = entry
            replaced = True
            break
    if not replaced:
        skins.append(entry)
    return save_my_skins(skins)


def _game_thumbnail_target_for_entry(entry: dict[str, Any], game_root: Path) -> tuple[Path | None, Path | None, str]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    car_content_path = str(entry.get("car_content_path") or "").strip()
    mechanical_rel = normalize_game_rel_path(str(files.get("mechanical_preset") or ""))
    visual_rel = normalize_game_rel_path(str(files.get("visual_preset") or ""))
    if not car_content_path:
        return None, None, "car content path missing"
    if not mechanical_rel or not visual_rel:
        return None, None, "mechanical or visual preset missing"
    target = generated_thumbnail_disk_path(game_root, car_content_path, mechanical_rel, visual_rel)
    return target, target.with_suffix(".texturemips"), ""


def _file_rel_values(files: dict[str, Any], kinds: set[str]) -> list[tuple[str, str]]:
    values: list[tuple[str, str]] = []
    for kind, raw in files.items():
        if kind not in kinds:
            continue
        rows = raw if isinstance(raw, list) else [raw]
        for row in rows:
            rel_norm = normalize_skin_asset_rel_path(str(row or ""))
            if rel_norm:
                values.append((kind, rel_norm))
    return values


def _unique_existing_mechanical_preset_refs(game_root: Path, refs: Iterable[Any]) -> list[str]:
    unique: list[str] = []
    seen: set[str] = set()
    for ref in refs:
        normalized = normalize_game_rel_path(ref)
        if not normalized or not normalized.lower().endswith(".mechanicalcarpreset"):
            continue
        if not _disk_from_game_rel(game_root, normalized):
            continue
        key = normalized.lower()
        if key in seen:
            continue
        seen.add(key)
        unique.append(normalized)
    return unique


def _proto_content_refs(data: bytes, suffixes: tuple[str, ...] | None = None) -> list[str]:
    suffixes_lc = tuple(str(suffix or "").lower() for suffix in (suffixes or ()))
    refs: list[str] = []
    seen: set[str] = set()
    try:
        rows = collect_proto_strings(data)
    except Exception:
        rows = []
    for _path, value in rows:
        ref = normalize_game_rel_path(str(value or ""))
        ref_lc = ref.lower()
        if not ref_lc.startswith("content\\"):
            continue
        if suffixes_lc and not any(ref_lc.endswith(suffix) for suffix in suffixes_lc):
            continue
        if ref_lc in seen:
            continue
        seen.add(ref_lc)
        refs.append(ref)
    return refs


def _carpart_ref_from_compatiblepart(game_root: Path, compatible_rel: str) -> str:
    path = resolve_content_path(game_root, compatible_rel)
    if not path.exists():
        return ""
    refs = _proto_content_refs(path.read_bytes(), (".carpart",))
    return refs[0] if refs else ""


def _mesh_refs_from_carpart(game_root: Path, carpart_rel: str) -> list[str]:
    path = resolve_content_path(game_root, carpart_rel)
    if not path.exists():
        return []
    return _proto_content_refs(path.read_bytes(), (".mesh",))


def _active_compatiblepart_refs_for_entry(entry: dict[str, Any], game_root: Path) -> list[str]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    refs: list[str] = []
    for raw in (
        files.get("mechanical_preset"),
        source_skin.get("mechanical_preset"),
        files.get("carfinalstate"),
        source_skin.get("carfinalstate"),
    ):
        rel = normalize_game_rel_path(str(raw or ""))
        if not rel:
            continue
        path = resolve_content_path(game_root, rel)
        if path.exists():
            refs.extend(_proto_content_refs(path.read_bytes(), (".compatiblepart",)))
    for _kind, rel in _file_rel_values(files, {"carfinalstates"}):
        path = resolve_content_path(game_root, rel)
        if path.exists():
            refs.extend(_proto_content_refs(path.read_bytes(), (".compatiblepart",)))
    return variant_parts.unique_compatible_part_refs(refs)


def _actor_compatiblepart_refs_for_entry(entry: dict[str, Any], game_root: Path) -> list[str]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    actor_rel = normalize_game_rel_path(str(files.get("actor") or ""))
    car_content_path = str(entry.get("car_content_path") or f"content\\cars\\{entry.get('car_model') or ''}").strip()
    actor_path = resolve_content_path(game_root, actor_rel) if actor_rel else None
    if not (actor_path and actor_path.exists()):
        actor_path = find_actor_file(game_root, car_content_path)
    if not (actor_path and actor_path.exists()):
        return []
    try:
        refs = _proto_content_refs(actor_path.read_bytes(), (".compatiblepart",))
    except Exception:
        refs = []
    filtered = [
        normalize_game_rel_path(ref)
        for ref in refs
        if "\\presets\\parts\\" in normalize_game_rel_path(ref).lower()
        or "\\presets\\aftermarket\\" in normalize_game_rel_path(ref).lower()
    ]
    return variant_parts.unique_compatible_part_refs(filtered)


def _mesh_has_skin_ranges(mesh_uv: dict[str, Any]) -> bool:
    for row in mesh_uv.get("material_ranges", []) or []:
        if not isinstance(row, dict):
            continue
        text = " ".join(
            str(row.get(key) or "")
            for key in ("name", "material", "materialPath", "resolvedMaterialPath")
        ).lower()
        if "skin" in text:
            return True
    return False


def _variant_part_mesh_is_preview_candidate(mesh_rel: str, carpart_rel: str, skin_relevant: bool) -> bool:
    text = f"{mesh_rel} {carpart_rel}".lower()
    if any(token in text for token in ("plate", "steer", "rim", "tyre", "tire", "physics")):
        return False
    if "diffusor" in text or "diffuser" in text:
        return False
    if skin_relevant:
        return True
    return any(
        token in text
        for token in (
            "body",
            "bumper",
            "fbumper",
            "rbumper",
            "hardtop",
            "hood",
            "bonnet",
            "frontlip",
            "front_lip",
            "lip",
            "wing",
            "spoiler",
            "splitter",
            "fender",
            "canard",
            "skirt",
        )
    )


SUPRA_MKIV_DRIFT_PART_PREVIEW_TRANSFORMS = {
    "toyota_supra_mkiv_ext_drift_body": {"x": 0, "y": 0, "z": 0, "rx": 0, "ry": 0, "rz": 0, "sx": 1, "sy": 1, "sz": 1},
    "toyota_supra_mkiv_ext_drift_fbumper": {"x": 0, "y": 0.6, "z": 2.08, "rx": 0, "ry": 0, "rz": 0, "sx": 1, "sy": 1, "sz": 1},
    "toyota_supra_mkiv_ext_drift_hood": {"x": 0, "y": 0.82, "z": 0.28, "rx": 0, "ry": 0, "rz": 0, "sx": 1, "sy": 1, "sz": 1},
    "toyota_supra_mkiv_ext_drift_wing": {"x": 0, "y": 0.9, "z": -2.08, "rx": 0, "ry": 0, "rz": 0, "sx": 1, "sy": 1, "sz": 1},
}


def _variant_part_identity_transform() -> dict[str, float]:
    return {"x": 0.0, "y": 0.0, "z": 0.0, "rx": 0.0, "ry": 0.0, "rz": 0.0, "sx": 1.0, "sy": 1.0, "sz": 1.0}


def _variant_part_transform_with_meta(transform: dict[str, float], source: str = "", mount: str = "") -> dict[str, Any]:
    data: dict[str, Any] = {key: float(value) for key, value in _variant_part_identity_transform().items()}
    for key in data:
        if key in transform:
            data[key] = float(transform.get(key) or 0.0) if key not in {"sx", "sy", "sz"} else float(transform.get(key) or 1.0)
    if source:
        data["source"] = source
    if mount:
        data["mount"] = mount
    return data


def _variant_part_preview_transform(entry: dict[str, Any], mesh_ref: str) -> dict[str, Any]:
    identity = _variant_part_identity_transform()
    car_model = str(entry.get("car_model") or "").strip().lower()
    stem = Path(str(mesh_ref or "").replace("\\", "/")).stem.lower()
    calibration = {}
    if car_model == "ks_toyota_supra_mkiv":
        calibration = SUPRA_MKIV_DRIFT_PART_PREVIEW_TRANSFORMS.get(stem, {})
    if not calibration:
        return identity
    return _variant_part_transform_with_meta({key: float(calibration.get(key, fallback)) for key, fallback in identity.items()}, "calibration")


def _compatiblepart_mount_name(game_root: Path, compatible_rel: str) -> str:
    path = resolve_content_path(game_root, compatible_rel)
    if not path.exists():
        return ""
    try:
        for field in parse_fields(path.read_bytes()):
            if field.number == 3 and field.wire == 2:
                return str(is_printable_utf8(field.value) or "").strip()
    except Exception:
        return ""
    return ""


def _mesh_node_transforms(mesh_path: Path) -> dict[str, dict[str, Any]]:
    if not mesh_path.exists():
        return {}
    try:
        top_fields = parse_fields(mesh_path.read_bytes())
        main_field = next((field for field in top_fields if field.number == 5 and field.wire == 2), None)
        if main_field is None:
            return {}
        main_fields = parse_fields(bytes(main_field.value))
    except Exception:
        return {}
    nodes: dict[str, dict[str, Any]] = {}
    for field in main_fields:
        if field.number != 12 or field.wire != 2:
            continue
        try:
            node_fields = parse_fields(bytes(field.value))
        except Exception:
            continue
        name = ""
        parent = ""
        matrix: dict[int, float] = {}
        for node_field in node_fields:
            if node_field.number == 1 and node_field.wire == 2:
                name = str(is_printable_utf8(node_field.value) or "").strip()
            elif node_field.number == 2 and node_field.wire == 2:
                parent = str(is_printable_utf8(node_field.value) or "").strip()
            elif node_field.number == 3 and node_field.wire == 2:
                try:
                    for matrix_field in parse_fields(bytes(node_field.value)):
                        if matrix_field.wire == 5 and isinstance(matrix_field.value, (bytes, bytearray)) and len(matrix_field.value) == 4:
                            matrix[int(matrix_field.number)] = float(struct.unpack("<f", bytes(matrix_field.value))[0])
                except Exception:
                    pass
        if name:
            nodes[name.upper()] = {
                "name": name,
                "parent": parent,
                "matrix": matrix,
            }
    def world_translation(key: str, trail: set[str] | None = None) -> tuple[float, float, float]:
        trail = set(trail or set())
        clean_key = str(key or "").upper()
        if not clean_key or clean_key in trail:
            return (0.0, 0.0, 0.0)
        node = nodes.get(clean_key)
        if not node:
            return (0.0, 0.0, 0.0)
        matrix = node.get("matrix") if isinstance(node.get("matrix"), dict) else {}
        local = (
            float(matrix.get(13, 0.0) or 0.0),
            float(matrix.get(14, 0.0) or 0.0),
            float(matrix.get(15, 0.0) or 0.0),
        )
        parent = str(node.get("parent") or "").strip().upper()
        if not parent:
            return local
        parent_world = world_translation(parent, trail | {clean_key})
        return (
            local[0] + parent_world[0],
            local[1] + parent_world[1],
            local[2] + parent_world[2],
        )

    for key, node in nodes.items():
        x, y, z = world_translation(key)
        node["transform"] = _variant_part_transform_with_meta(
            {"x": x, "y": y, "z": z, "sx": 1.0, "sy": 1.0, "sz": 1.0},
            "mesh-node",
            str(node.get("name") or key),
        )
    return nodes


def _variant_part_should_apply_mount_transform(row: dict[str, Any], transform: dict[str, Any]) -> bool:
    offset = (float(transform.get("x") or 0.0), float(transform.get("y") or 0.0), float(transform.get("z") or 0.0))
    if max(abs(value) for value in offset) < 0.05:
        return False
    bounds_min = row.get("boundsMin")
    bounds_max = row.get("boundsMax")
    if not (isinstance(bounds_min, list) and isinstance(bounds_max, list) and len(bounds_min) >= 3 and len(bounds_max) >= 3):
        return False
    try:
        center = tuple((float(bounds_min[index]) + float(bounds_max[index])) / 2.0 for index in range(3))
    except Exception:
        return False
    origin_distance = math.sqrt(sum(value * value for value in center))
    mount_distance = math.sqrt(sum((center[index] - offset[index]) ** 2 for index in range(3)))
    return origin_distance + 0.25 < mount_distance


def _variant_part_replaces_exterior(entry: dict[str, Any], mesh_ref: str) -> bool:
    car_model = str(entry.get("car_model") or "").strip().lower()
    stem = Path(str(mesh_ref or "").replace("\\", "/")).stem.lower()
    return car_model == "ks_toyota_supra_mkiv" and stem == "toyota_supra_mkiv_ext_drift_body"


def _resolve_livery_variant_part_meshes(
    entry: dict[str, Any],
    game_root: Path,
    compatible_refs: list[str] | None = None,
    mesh_uv_loader: Any | None = None,
) -> dict[str, Any]:
    result: dict[str, Any] = {
        "compatibleParts": [],
        "meshRefs": [],
        "skinMeshRefs": [],
        "hideExteriorModel": False,
        "warnings": [],
    }
    car_content_path = str(entry.get("car_content_path") or entry.get("carContentPath") or "").strip()
    exterior_probe = preview3d.find_exterior_mesh(game_root, car_content_path) if car_content_path else {}
    exterior_mesh_path = Path(str(exterior_probe.get("path") or "")) if isinstance(exterior_probe, dict) else Path(str(exterior_probe or ""))
    mount_nodes = _mesh_node_transforms(exterior_mesh_path) if exterior_mesh_path else {}
    for compatible_rel in (compatible_refs if compatible_refs is not None else _active_compatiblepart_refs_for_entry(entry, game_root)):
        carpart_rel = _carpart_ref_from_compatiblepart(game_root, compatible_rel)
        if not carpart_rel:
            result["warnings"].append(f"Compatible part has no carpart reference: {compatible_rel}")
            continue
        mesh_refs = _mesh_refs_from_carpart(game_root, carpart_rel)
        mount_name = _compatiblepart_mount_name(game_root, compatible_rel)
        part_info = {
            "compatiblePartRel": compatible_rel,
            "carPartRel": carpart_rel,
            "mountName": mount_name,
            "meshRefs": mesh_refs,
        }
        result["compatibleParts"].append(part_info)
        for mesh_rel in mesh_refs:
            mesh_path = resolve_content_path(game_root, mesh_rel)
            row = {
                "label": preview3d.safe_segment(Path(mesh_rel.replace("\\", "/")).stem, "part"),
                "relPath": mesh_rel,
                "path": str(mesh_path),
                "exists": mesh_path.exists(),
                "compatiblePartRel": compatible_rel,
                "carPartRel": carpart_rel,
                "mountName": mount_name,
                "skinRelevant": False,
                "previewRelevant": False,
                "skinTriangleCount": 0,
                "transform": _variant_part_preview_transform(entry, mesh_rel),
                "replacesExterior": _variant_part_replaces_exterior(entry, mesh_rel),
            }
            if not mesh_path.exists():
                result["warnings"].append(f"Variant part mesh not found: {mesh_rel}")
                result["meshRefs"].append(row)
                continue
            try:
                mesh_uv = mesh_uv_loader(mesh_path, game_root) if mesh_uv_loader else inspect_mesh_uv_view(mesh_path, game_root)
                if mesh_uv.get("bounds_min") and mesh_uv.get("bounds_max"):
                    row["boundsMin"] = mesh_uv.get("bounds_min")
                    row["boundsMax"] = mesh_uv.get("bounds_max")
                skin_triangles = 0
                for material_range in mesh_uv.get("material_ranges", []) or []:
                    if not isinstance(material_range, dict):
                        continue
                    if _mesh_has_skin_ranges({"material_ranges": [material_range]}):
                        skin_triangles += int(material_range.get("triangleCount") or 0)
                row["skinRelevant"] = skin_triangles > 0
                row["skinTriangleCount"] = skin_triangles
            except Exception as exc:
                result["warnings"].append(f"Variant part mesh inspection failed: {mesh_rel}: {exc}")
            row["previewRelevant"] = _variant_part_mesh_is_preview_candidate(mesh_rel, carpart_rel, bool(row.get("skinRelevant")))
            mount_node = mount_nodes.get(mount_name.upper()) if mount_name else None
            if mount_node and _variant_part_should_apply_mount_transform(row, mount_node.get("transform") or {}):
                row["transform"] = mount_node.get("transform") or row.get("transform")
                row["mountTransformApplied"] = True
            if row.get("previewRelevant") and row.get("replacesExterior"):
                result["hideExteriorModel"] = True
            result["meshRefs"].append(row)
            if row.get("skinRelevant"):
                result["skinMeshRefs"].append(row)
    return result


def _variant_analysis_rel_path(game_root: Path, path: Path | None) -> str:
    if not path:
        return ""
    content_rel = content_rel_from_disk(game_root, path)
    return norm_slashes(content_rel or str(path))


def _variant_analysis_local_or_game_path(game_root: Path, entry: dict[str, Any] | None, rel_or_path: str | None) -> Path | None:
    text = str(rel_or_path or "").strip()
    if not text:
        return None
    raw_path = Path(text)
    if raw_path.is_absolute():
        return raw_path
    if entry:
        local_path = _local_skin_asset_path(entry, text)
        if local_path and local_path.exists():
            return local_path
    game_path = resolve_content_path(game_root, text)
    return game_path if game_path.exists() else None


def _variant_analysis_texture_slot(slot: dict[str, Any], game_root: Path, entry: dict[str, Any] | None = None) -> dict[str, Any]:
    resolved_text = str(slot.get("resolved_path") or slot.get("value") or "").strip()
    resolved = _variant_analysis_local_or_game_path(game_root, entry, resolved_text)
    return {
        "name": slot.get("name"),
        "channel": _preview_material_channel(str(slot.get("name") or "")),
        "value": slot.get("value"),
        "resolvedPath": str(resolved) if resolved else "",
        "resolvedRel": _variant_analysis_rel_path(game_root, resolved),
        "texturemipsPath": slot.get("texturemips_path") or "",
        "exists": bool(resolved and resolved.exists()),
    }


def _variant_analysis_material_summary(game_root: Path, material_path: Path | None, source: str = "", entry: dict[str, Any] | None = None) -> dict[str, Any]:
    summary: dict[str, Any] = {
        "source": source,
        "path": str(material_path) if material_path else "",
        "relPath": _variant_analysis_rel_path(game_root, material_path),
        "exists": bool(material_path and material_path.exists()),
        "shader": "",
        "parentMaterialPath": "",
        "textureSlots": [],
        "colorTextureRefs": [],
        "aoTextureRefs": [],
        "detailTextureRefs": [],
    }
    if not material_path or not material_path.exists():
        return summary
    try:
        material = inspect_material(material_path, root=game_root)
    except Exception as exc:
        summary["error"] = str(exc)
        return summary
    summary["shader"] = str(material.get("shader") or "")
    summary["parentMaterialPath"] = str(material.get("material_path") or "")
    slots = [
        _variant_analysis_texture_slot(slot, game_root, entry)
        for slot in material.get("texture_slots", []) or []
        if isinstance(slot, dict)
    ]
    summary["textureSlots"] = slots
    summary["colorTextureRefs"] = [slot for slot in slots if slot.get("channel") == "baseColor"]
    summary["aoTextureRefs"] = [slot for slot in slots if slot.get("channel") == "ao"]
    summary["detailTextureRefs"] = [slot for slot in slots if slot.get("channel") == "detailSplat"]
    return summary


def _variant_analysis_design_materials(game_root: Path, entry: dict[str, Any], slot_id: str = "EXT_SKIN") -> dict[str, Any]:
    result: dict[str, Any] = {"designPath": "", "slotId": slot_id, "materialRefs": [], "materials": [], "warnings": []}
    design = _find_design_for_skin(game_root, entry, local_only=False)
    if not design or not design.exists():
        result["warnings"].append("Design file was not found.")
        return result
    result["designPath"] = str(design)
    try:
        slots = extract_design_slot_materials(design.read_bytes())
    except Exception as exc:
        result["warnings"].append(f"Design material extraction failed: {exc}")
        return result
    material_refs = slots.get(slot_id) or slots.get(slot_id.upper()) or []
    result["materialRefs"] = [norm_slashes(str(ref or "")) for ref in material_refs if str(ref or "").strip()]
    for material_ref in result["materialRefs"]:
        material_path = _variant_analysis_local_or_game_path(game_root, entry, material_ref) or find_material_file(game_root, material_ref)
        result["materials"].append(_variant_analysis_material_summary(game_root, material_path, source="design", entry=entry))
    return result


def _variant_analysis_base_skin_material(game_root: Path, entry: dict[str, Any]) -> dict[str, Any]:
    car_content_path = str(entry.get("car_content_path") or f"content\\cars\\{entry.get('car_model') or ''}").strip()
    material = find_material_file(game_root, f"{car_content_path}\\materials\\EXT_SKIN.material") if car_content_path else None
    return _variant_analysis_material_summary(game_root, material, source="base-car")


def _variant_analysis_mesh_materials(
    game_root: Path,
    mesh_path: Path,
    mesh_uv_loader: Any | None = None,
) -> list[dict[str, Any]]:
    try:
        mesh_uv = mesh_uv_loader(mesh_path, game_root) if mesh_uv_loader else inspect_mesh_uv_view(mesh_path, game_root)
    except Exception:
        return []
    summaries: list[dict[str, Any]] = []
    seen: set[str] = set()
    for material_range in mesh_uv.get("material_ranges", []) or []:
        if not isinstance(material_range, dict):
            continue
        material_ref = str(material_range.get("materialPath") or material_range.get("material") or "").strip()
        if not material_ref:
            continue
        material_path = resolve_content_path(game_root, material_ref)
        key = str(material_path or material_ref).lower()
        if key in seen:
            continue
        seen.add(key)
        summary = _variant_analysis_material_summary(game_root, material_path, source="mesh")
        summary["rangeName"] = material_range.get("name") or ""
        summary["triangleCount"] = int(material_range.get("triangleCount") or 0)
        summaries.append(summary)
    return summaries


def _variant_analysis_part_slot(compatible_rel: str, carpart_rel: str) -> str:
    return variant_parts.part_slot(compatible_rel, carpart_rel)


def _variant_analysis_is_uv_replacement_like_slot(slot: str) -> bool:
    return variant_parts.is_uv_replacement_like_slot(slot)


def _variant_analysis_ignored_reason(slot: str, mesh_rel: str, carpart_rel: str, skin_relevant: bool) -> str:
    return variant_parts.ignored_reason(slot, mesh_rel, carpart_rel, skin_relevant)


def _variant_analysis_texture_rel_set(materials: list[dict[str, Any]], key: str) -> set[str]:
    refs: set[str] = set()
    for material in materials:
        for slot in material.get(key, []) or []:
            rel = norm_slashes(str(slot.get("resolvedRel") or slot.get("value") or "")).lower()
            if rel:
                refs.add(rel)
    return refs


def _variant_analysis_active_compatiblepart_refs_for_entry(entry: dict[str, Any], game_root: Path) -> tuple[list[str], str]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    refs: list[str] = []
    for raw in (
        files.get("carfinalstate"),
        source_skin.get("carfinalstate"),
    ):
        rel = normalize_game_rel_path(str(raw or ""))
        if not rel:
            continue
        path = resolve_content_path(game_root, rel)
        if path.exists():
            refs.extend(_proto_content_refs(path.read_bytes(), (".compatiblepart",)))
    for _kind, rel in _file_rel_values(files, {"carfinalstates"}):
        path = resolve_content_path(game_root, rel)
        if path.exists():
            refs.extend(_proto_content_refs(path.read_bytes(), (".compatiblepart",)))
    unique = variant_parts.unique_compatible_part_refs(refs)
    if unique:
        return unique, "carfinalstate"
    fallback = _active_compatiblepart_refs_for_entry(entry, game_root)
    if fallback:
        return fallback, "preset-fallback"
    actor_refs = _actor_compatiblepart_refs_for_entry(entry, game_root)
    if actor_refs:
        return actor_refs, "actor-static"
    return [], "none"


def _classify_livery_variant_analysis(report: dict[str, Any]) -> dict[str, Any]:
    return variant_parts.classify_variant_report(report)


def inspect_livery_variant_part_analysis(skin_key: str, root: str | Path | None = None) -> dict[str, Any]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    game_root_text = str(root or entry.get("game_root") or get_default_root_folder()).strip()
    if not game_root_text:
        raise ValueError("Game root is required")
    game_root = normalize_game_root(game_root_text)
    if not game_root.exists():
        raise FileNotFoundError(f"Game root not found: {game_root}")

    warnings: list[str] = []
    base_material = _variant_analysis_base_skin_material(game_root, entry)
    active_design = _variant_analysis_design_materials(game_root, entry, "EXT_SKIN")
    warnings.extend(str(row) for row in active_design.get("warnings", []) or [])
    active_materials = active_design.get("materials", []) if isinstance(active_design.get("materials"), list) else []

    base_ao_refs = _variant_analysis_texture_rel_set([base_material], "aoTextureRefs")
    base_color_refs = _variant_analysis_texture_rel_set([base_material], "colorTextureRefs")
    active_ao_refs = _variant_analysis_texture_rel_set(active_materials, "aoTextureRefs")
    active_color_refs = _variant_analysis_texture_rel_set(active_materials, "colorTextureRefs")
    active_detail_refs = _variant_analysis_texture_rel_set(active_materials, "detailTextureRefs")
    compatible_refs, compatible_source = _variant_analysis_active_compatiblepart_refs_for_entry(entry, game_root)
    variant_parts = _resolve_livery_variant_part_meshes(entry, game_root, compatible_refs=compatible_refs)
    warnings.extend(str(row) for row in variant_parts.get("warnings", []) or [])

    mesh_rows: list[dict[str, Any]] = []
    livery_relevant_mesh_refs: list[str] = []
    ignored_mesh_refs: list[dict[str, str]] = []
    mesh_materials_by_ref: dict[str, list[dict[str, Any]]] = {}
    for row in variant_parts.get("meshRefs", []) or []:
        if not isinstance(row, dict):
            continue
        mesh_rel = norm_slashes(str(row.get("relPath") or ""))
        carpart_rel = norm_slashes(str(row.get("carPartRel") or ""))
        compatible_rel = norm_slashes(str(row.get("compatiblePartRel") or ""))
        slot = _variant_analysis_part_slot(compatible_rel, carpart_rel)
        skin_relevant = bool(row.get("skinRelevant"))
        ignored_reason = _variant_analysis_ignored_reason(slot, mesh_rel, carpart_rel, skin_relevant)
        mesh_path = Path(str(row.get("path") or ""))
        material_summaries = _variant_analysis_mesh_materials(game_root, mesh_path) if mesh_path.exists() else []
        mesh_materials_by_ref[mesh_rel] = material_summaries
        mesh_ao_refs = _variant_analysis_texture_rel_set(material_summaries, "aoTextureRefs")
        mesh_color_refs = _variant_analysis_texture_rel_set(material_summaries, "colorTextureRefs")
        livery_relevant = bool(not ignored_reason and (skin_relevant or (mesh_color_refs & (base_color_refs | active_color_refs))))
        mesh_report = {
            "relPath": mesh_rel,
            "path": row.get("path") or "",
            "exists": bool(row.get("exists")),
            "slot": slot,
            "compatiblePartRel": compatible_rel,
            "carPartRel": carpart_rel,
            "skinRelevant": skin_relevant,
            "previewRelevant": bool(row.get("previewRelevant")),
            "liveryRelevant": livery_relevant,
            "ignoredReason": ignored_reason,
            "replacesExterior": bool(row.get("replacesExterior")),
            "skinTriangleCount": int(row.get("skinTriangleCount") or 0),
            "colorTextureRefs": sorted(mesh_color_refs),
            "aoTextureRefs": sorted(mesh_ao_refs),
            "materialRefs": [
                norm_slashes(str(material.get("relPath") or material.get("path") or ""))
                for material in material_summaries
            ],
        }
        mesh_rows.append(mesh_report)
        if livery_relevant:
            livery_relevant_mesh_refs.append(mesh_rel)
        elif ignored_reason:
            ignored_mesh_refs.append({"relPath": mesh_rel, "reason": ignored_reason})

    compatible_parts = []
    for part in variant_parts.get("compatibleParts", []) or []:
        if not isinstance(part, dict):
            continue
        compatible_rel = norm_slashes(str(part.get("compatiblePartRel") or ""))
        carpart_rel = norm_slashes(str(part.get("carPartRel") or ""))
        compatible_parts.append({
            "compatiblePartRel": compatible_rel,
            "carPartRel": carpart_rel,
            "slot": _variant_analysis_part_slot(compatible_rel, carpart_rel),
            "meshRefs": [norm_slashes(str(ref or "")) for ref in part.get("meshRefs", []) or []],
        })

    report: dict[str, Any] = {
        "carModel": str(entry.get("car_model") or ""),
        "skinKey": str(skin_key or ""),
        "skinId": str(entry.get("skin_id") or ""),
        "gameRoot": str(game_root),
        "base": {
            "materialRefs": [norm_slashes(str(base_material.get("relPath") or ""))] if base_material.get("relPath") else [],
            "materials": [base_material] if base_material.get("exists") else [],
            "colorTextureRefs": sorted(base_color_refs),
            "aoTextureRefs": sorted(base_ao_refs),
        },
        "activeSkin": {
            "designPath": active_design.get("designPath") or "",
            "materialRefs": active_design.get("materialRefs") or [],
            "materials": active_materials,
            "colorTextureRefs": sorted(active_color_refs),
            "aoTextureRefs": sorted(active_ao_refs),
            "detailTextureRefs": sorted(active_detail_refs),
        },
        "variant": {
            "compatiblePartSource": compatible_source,
            "compatibleParts": compatible_parts,
            "meshRefs": mesh_rows,
            "liveryRelevantMeshRefs": sorted(set(livery_relevant_mesh_refs)),
            "ignoredMeshRefs": ignored_mesh_refs,
            "materialRefs": sorted({
                ref
                for refs in mesh_materials_by_ref.values()
                for material in refs
                for ref in [norm_slashes(str(material.get("relPath") or material.get("path") or ""))]
                if ref
            }),
            "colorTextureRefs": sorted({
                ref
                for refs in mesh_materials_by_ref.values()
                for ref in _variant_analysis_texture_rel_set(refs, "colorTextureRefs")
            } | active_color_refs),
            "aoTextureRefs": sorted({
                ref
                for refs in mesh_materials_by_ref.values()
                for ref in _variant_analysis_texture_rel_set(refs, "aoTextureRefs")
            } | active_ao_refs),
        },
        "warnings": warnings,
    }
    report.update(_classify_livery_variant_analysis(report))
    return _json_ready(report)


def _actor_mechanical_preset_refs(game_root: Path, actor_path: Path | None) -> list[str]:
    if not (actor_path and actor_path.exists()):
        return []
    try:
        return _unique_existing_mechanical_preset_refs(game_root, actor_list_mechanicalpresets(actor_path.read_bytes()))
    except Exception:
        return []


def _stored_mechanical_preset_refs_for_entry(entry: dict[str, Any], game_root: Path) -> list[str]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    refs: list[Any] = []
    stored = files.get("mechanical_presets")
    if isinstance(stored, list):
        refs.extend(stored)
    refs.append(files.get("mechanical_preset"))
    refs.append(source_skin.get("mechanical_preset"))
    return _unique_existing_mechanical_preset_refs(game_root, refs)


def _mechanical_preset_has_visual(game_root: Path, mechanical_rel: str, visual_rel: str) -> bool:
    mechanical_path = _disk_from_game_rel(game_root, mechanical_rel)
    if not (mechanical_path and mechanical_path.exists()):
        return False
    try:
        info = extract_mechanicalpreset_info(mechanical_path.read_bytes())
    except Exception:
        return False
    target = norm_slashes(visual_rel)
    return any(norm_slashes(str(row or "")) == target for row in info.get("visual_presets", []))


def _mechanical_preset_display_name(game_root: Path, mechanical_rel: str) -> str:
    mechanical_path = _disk_from_game_rel(game_root, mechanical_rel)
    if mechanical_path and mechanical_path.exists():
        try:
            info = extract_mechanicalpreset_info(mechanical_path.read_bytes())
            return str(info.get("short_name") or info.get("name") or mechanical_path.stem).strip() or mechanical_path.stem
        except Exception:
            return mechanical_path.stem
    return Path(norm_slashes(mechanical_rel).replace("\\", "/")).stem or str(mechanical_rel)


def _mechanical_variant_notice(game_root: Path, all_refs: list[str], target_refs: list[str]) -> dict[str, Any]:
    all_clean = _unique_existing_mechanical_preset_refs(game_root, all_refs)
    target_clean = _unique_existing_mechanical_preset_refs(game_root, target_refs)
    all_names = [_mechanical_preset_display_name(game_root, ref) for ref in all_clean]
    target_names = [_mechanical_preset_display_name(game_root, ref) for ref in target_clean]
    limited = bool(all_clean and target_clean and len(target_clean) < len(all_clean))
    if not target_clean:
        message = "No compatible vehicle preset could be determined for this livery."
    elif limited:
        message = "This livery is limited to the matching vehicle preset: " + ", ".join(target_names)
    elif len(target_clean) > 1:
        message = "This livery will be available for matching vehicle presets: " + ", ".join(target_names)
    else:
        message = "This livery will be available for vehicle preset: " + ", ".join(target_names)
    return {
        "limited": limited,
        "message": message,
        "targetMechanicalPresets": target_clean,
        "targetNames": target_names,
        "allMechanicalPresets": all_clean,
        "allNames": all_names,
        "targetCount": len(target_clean),
        "totalCount": len(all_clean),
    }


def _known_mechanical_preset_refs_for_entry(entry: dict[str, Any], game_root: Path, actor_path: Path | None) -> list[str]:
    refs = []
    refs.extend(_stored_mechanical_preset_refs_for_entry(entry, game_root))
    refs.extend(_actor_mechanical_preset_refs(game_root, actor_path))
    return _unique_existing_mechanical_preset_refs(game_root, refs)


def _is_liverylab_managed_mechanical_ref(rel: str) -> bool:
    stem = Path(norm_slashes(str(rel or "")).replace("\\", "/")).stem.lower()
    return stem.startswith("liverylab_") or "_liverylab_" in stem or "liverylab_window_part" in stem


def _target_mechanical_preset_refs_for_entry(entry: dict[str, Any], game_root: Path, actor_path: Path | None) -> list[str]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    actor_refs = _actor_mechanical_preset_refs(game_root, actor_path)
    stored_refs = _stored_mechanical_preset_refs_for_entry(entry, game_root)
    source_visual_rel = normalize_game_rel_path(str(source_skin.get("visual_preset") or ""))
    if source_visual_rel and actor_refs:
        matching = [
            ref
            for ref in actor_refs
            if not _is_liverylab_managed_mechanical_ref(ref)
            and _mechanical_preset_has_visual(game_root, ref, source_visual_rel)
        ]
        if matching:
            return matching
    if stored_refs:
        source_mechanical_rel = normalize_game_rel_path(str(source_skin.get("mechanical_preset") or ""))
        if source_mechanical_rel:
            source_match = [ref for ref in stored_refs if ref.lower() == source_mechanical_rel.lower()]
            if source_match:
                return source_match
        single = normalize_game_rel_path(str(files.get("mechanical_preset") or ""))
        if single:
            single_match = [ref for ref in stored_refs if ref.lower() == single.lower()]
            if single_match:
                return single_match
        return stored_refs
    return actor_refs


def _game_ref_key(ref: Any) -> str:
    return norm_slashes(normalize_game_rel_path(str(ref or ""))).lower()


def _mechanical_preset_refs_for_visual_refs(
    entry: dict[str, Any],
    game_root: Path,
    actor_path: Path | None,
    visual_refs: Iterable[Any],
) -> list[str]:
    car_content_path = str(entry.get("car_content_path") or "").strip()
    target_visuals = {_game_ref_key(ref) for ref in visual_refs if _game_ref_key(ref)}
    refs: list[Any] = []
    refs.extend(_target_mechanical_preset_refs_for_entry(entry, game_root, actor_path))
    refs.extend(_known_mechanical_preset_refs_for_entry(entry, game_root, actor_path))
    if car_content_path and target_visuals:
        presets_dir = game_root / Path(*split_content_path(car_content_path)) / "presets"
        if presets_dir.exists():
            try:
                for path in presets_dir.glob("*.mechanicalcarpreset"):
                    if not path.is_file():
                        continue
                    try:
                        info = extract_mechanicalpreset_info(path.read_bytes())
                    except Exception:
                        continue
                    if any(_game_ref_key(row) in target_visuals for row in info.get("visual_presets", [])):
                        rel = content_rel_from_disk(game_root, path)
                        if rel:
                            refs.append(rel)
            except Exception:
                pass
    return _unique_existing_mechanical_preset_refs(game_root, refs)


def _carfinalstate_paths_for_visual_refs(
    game_root: Path,
    visual_refs: Iterable[Any],
    mechanical_refs: Iterable[Any] | None = None,
) -> list[Path]:
    target_visuals = {_game_ref_key(ref) for ref in visual_refs if _game_ref_key(ref)}
    if not target_visuals:
        return []
    target_mechanicals = {_game_ref_key(ref) for ref in (mechanical_refs or []) if _game_ref_key(ref)}
    paths: list[Path] = []
    seen: set[str] = set()
    search_dirs: list[Path] = []
    seen_dirs: set[str] = set()
    for ref in visual_refs:
        for folder in _carfinalstate_search_dirs(game_root, visual_rel=str(ref or "")):
            key = str(folder.resolve()).lower() if folder.exists() else str(folder).lower()
            if key in seen_dirs:
                continue
            seen_dirs.add(key)
            search_dirs.append(folder)
    try:
        for folder in search_dirs:
            if not folder.exists():
                continue
            for path in folder.rglob("*.carfinalstate"):
                if not path.is_file():
                    continue
                try:
                    info = extract_carfinalstate_info(path.read_bytes())
                except Exception:
                    continue
                if _game_ref_key(info.get("visual_preset")) not in target_visuals:
                    continue
                if target_mechanicals and _game_ref_key(info.get("mechanical_preset")) not in target_mechanicals:
                    continue
                key = str(path.resolve()).lower()
                if key in seen:
                    continue
                seen.add(key)
                paths.append(path)
    except Exception:
        pass
    return paths


def _generated_thumbnail_paths_for_visual_refs(
    entry: dict[str, Any],
    game_root: Path,
    visual_refs: Iterable[Any],
    mechanical_refs: Iterable[Any],
) -> list[Path]:
    car_content_path = str(entry.get("car_content_path") or "").strip()
    if not car_content_path:
        return []
    paths: list[Path] = []
    seen: set[str] = set()
    for visual_ref in visual_refs:
        visual_rel = normalize_game_rel_path(str(visual_ref or ""))
        if not visual_rel.lower().endswith(".visualcarpreset"):
            continue
        for mechanical_ref in mechanical_refs:
            mechanical_rel = normalize_game_rel_path(str(mechanical_ref or ""))
            if not mechanical_rel.lower().endswith(".mechanicalcarpreset"):
                continue
            texture = generated_thumbnail_disk_path(game_root, car_content_path, mechanical_rel, visual_rel)
            for path in (texture, texture.with_suffix(".texturemips")):
                key = str(path.resolve()).lower()
                if key in seen:
                    continue
                seen.add(key)
                if path.exists() and path.is_file():
                    paths.append(path)
    return paths


def _design_material_replacements_between(
    source_design_data: bytes,
    target_design_data: bytes,
    *,
    local_skin_prefix: str = "",
) -> dict[str, str]:
    source_slots = extract_design_slot_materials(source_design_data)
    target_slots = extract_design_slot_materials(target_design_data)
    replacements: dict[str, str] = {}
    local_prefix = normalize_game_rel_path(local_skin_prefix).rstrip("\\")
    local_prefix_key = local_prefix.lower()
    for slot_id, source_materials in source_slots.items():
        target_materials = target_slots.get(slot_id) or []
        for index, source_material in enumerate(source_materials):
            if index >= len(target_materials):
                continue
            source_rel = normalize_game_rel_path(source_material)
            target_rel = normalize_game_rel_path(target_materials[index])
            if not source_rel or not target_rel or source_rel.lower() == target_rel.lower():
                continue
            if local_prefix_key and not target_rel.lower().startswith(local_prefix_key + "\\"):
                continue
            replacements[norm_slashes(source_rel)] = norm_slashes(target_rel)
    return replacements


def _skin_design_material_replacements(entry: dict[str, Any], game_root: Path) -> dict[str, str]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    source_design_rel = normalize_game_rel_path(str(source_skin.get("design") or ""))
    target_design_rel = normalize_game_rel_path(str(files.get("design") or ""))
    if not source_design_rel or not target_design_rel:
        return {}
    source_design = resolve_content_path(game_root, source_design_rel)
    target_design = _local_skin_asset_path(entry, target_design_rel) or _disk_from_game_rel(game_root, target_design_rel)
    if not (source_design.exists() and target_design and target_design.exists()):
        return {}
    skin_folder_rel = normalize_game_rel_path(str(files.get("skin_folder") or ""))
    if not skin_folder_rel:
        car_content_path = normalize_game_rel_path(str(entry.get("car_content_path") or source_skin.get("car_content_path") or ""))
        skin_id = str(entry.get("skin_id") or "").strip()
        if car_content_path and skin_id:
            skin_folder_rel = norm_slashes(f"{car_content_path}\\skins\\{skin_id}")
    try:
        return _design_material_replacements_between(
            source_design.read_bytes(),
            target_design.read_bytes(),
            local_skin_prefix=skin_folder_rel,
        )
    except Exception:
        return {}


def _driver_skin_sync_design_material_replacements(sync: dict[str, Any]) -> dict[str, str]:
    plan = sync.get("plan") if isinstance(sync.get("plan"), dict) else {}
    replacements: dict[str, str] = {}
    for row in plan.get("design_slots") or []:
        if not isinstance(row, dict):
            continue
        source_ref = normalize_game_rel_path(str(row.get("source_ref") or ""))
        target_ref = normalize_game_rel_path(str(row.get("target_ref") or ""))
        if source_ref and target_ref and source_ref.lower() != target_ref.lower():
            replacements[norm_slashes(source_ref)] = norm_slashes(target_ref)
    return replacements


def _my_skin_deactivation_cleanup_plan(
    entry: dict[str, Any],
    game_root: Path,
    actor_path: Path | None,
    visual_refs: Iterable[Any],
) -> dict[str, Any]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    visual_list = [normalize_game_rel_path(str(ref or "")) for ref in visual_refs if normalize_game_rel_path(str(ref or ""))]
    mechanical_rels = _mechanical_preset_refs_for_visual_refs(entry, game_root, actor_path, visual_list)
    matrix_paths: list[Path] = []
    seen_paths: set[str] = set()

    def add_path(path: Path | None) -> None:
        if not (path and path.exists() and path.is_file()):
            return
        key = str(path.resolve()).lower()
        if key in seen_paths:
            return
        seen_paths.add(key)
        matrix_paths.append(path)

    for _kind, rel_norm in _file_rel_values(
        files,
        {"carfinalstate", "carfinalstates", "thumbnail", "thumbnail_mips", "thumbnails", "thumbnail_mips_list"},
    ):
        add_path(game_root / Path(*split_content_path(rel_norm)))
    for path in _carfinalstate_paths_for_visual_refs(game_root, visual_list, mechanical_rels):
        add_path(path)
    for path in _generated_thumbnail_paths_for_visual_refs(entry, game_root, visual_list, mechanical_rels):
        add_path(path)

    return {
        "mechanical_rels": mechanical_rels,
        "mechanical_paths": [path for path in (_disk_from_game_rel(game_root, rel) for rel in mechanical_rels) if path],
        "matrix_paths": matrix_paths,
    }


def _write_skin_carfinalstate_for_mechanical(
    entry: dict[str, Any],
    game_root: Path,
    mechanical_rel: str,
    visual_rel: str,
    writer: GameFileWriter,
    *,
    template_mechanical_rel: str | None = None,
) -> dict[str, Any]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    car_content_path = str(entry.get("car_content_path") or "").strip()
    material_rel = normalize_game_rel_path(str(files.get("material") or ""))
    actor_rel = normalize_game_rel_path(str(files.get("actor") or ""))
    target_name = make_carfinalstate_filename(car_content_path, mechanical_rel, visual_rel)
    target_rel = carfinalstate_target_rel(car_content_path, mechanical_rel, visual_rel)
    target = game_root / Path(*split_content_path(target_rel))
    template_mechanical = normalize_game_rel_path(str(template_mechanical_rel or mechanical_rel))
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    source_visual_rel = normalize_game_rel_path(str(source_skin.get("visual_preset") or ""))
    template: Path | None = None
    using_local_template = False
    local_template = _local_skin_asset_path(entry, target_rel)
    if not (local_template and local_template.exists() and local_template.is_file()):
        local_template = _local_skin_asset_path(entry, f"system\\savedcars\\{target_name}")
    if local_template and local_template.exists() and local_template.is_file():
        template = local_template
        using_local_template = True
    if template is None and source_visual_rel:
        template = find_carfinalstate_for_visual(game_root, source_visual_rel, template_mechanical)
    if template is None and target.exists():
        template = target
    if template is None:
        template = find_carfinalstate_template_for_car(game_root, car_content_path, template_mechanical)
    if template is None or not template.exists():
        return {"status": "skipped", "reason": "carfinalstate template missing", "mechanical": mechanical_rel}
    old_bytes = template.read_bytes()
    old_info = extract_carfinalstate_info(old_bytes)
    readable = inspect_carfinalstate_readable_bytes(old_bytes)
    replacements = {
        norm_slashes(str(old_info.get("visual_preset") or "")): norm_slashes(visual_rel),
        norm_slashes(str(old_info.get("mechanical_preset") or "")): norm_slashes(mechanical_rel),
    }
    old_material = norm_slashes(str(readable.get("ext_skin_material") or ""))
    source_material = norm_slashes(str(source_skin.get("material") or ""))
    if material_rel:
        if old_material:
            replacements[old_material] = norm_slashes(material_rel)
        if source_material:
            replacements[source_material] = norm_slashes(material_rel)
    replacements.update(_skin_design_material_replacements(entry, game_root))
    replacements = {key: value for key, value in replacements.items() if key and value}
    local_template_is_current = (
        using_local_template
        and normalize_game_rel_path(str(old_info.get("mechanical_preset") or "")).lower() == mechanical_rel.lower()
        and normalize_game_rel_path(str(old_info.get("visual_preset") or "")).lower() == visual_rel.lower()
        and (not actor_rel or normalize_game_rel_path(str(old_info.get("actor_path") or "")).lower() == actor_rel.lower())
        and (not material_rel or old_material.lower() == material_rel.lower())
    )
    if local_template_is_current:
        new_data = edit_carfinalstate_data(old_bytes, replacements=replacements)
    else:
        new_data = edit_carfinalstate_data(
            old_bytes,
            car_data=str(old_info.get("car_data") or ""),
            actor_path=actor_rel or str(old_info.get("actor_path") or ""),
            mechanical_preset=mechanical_rel,
            visual_preset=visual_rel,
            compatible_rim=str(old_info.get("compatible_rim") or ""),
            rim_design=str(old_info.get("rim_design") or ""),
            replacements=replacements,
        )
    banner_material_rel = _entry_ext_banner_material_rel(entry, game_root)
    if banner_material_rel:
        new_data, _banner_changed = ext_banner.ensure_carfinalstate_ext_banner_snapshot(
            new_data,
            banner_material_rel,
            _entry_ext_banner_label(entry, game_root),
        )
    if _is_f2004_entry(entry):
        f2004_snapshot_materials: dict[str, str] = {}
        files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
        skin_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
        if skin_rel:
            for slot_key, spec in F2004_NEUTRAL_MATERIAL_SLOTS.items():
                f2004_snapshot_materials[norm_slashes(f"{skin_rel}\\{spec['material']}")] = str(spec.get("label") or slot_key)
        if f2004_snapshot_materials:
            new_data, _f2004_changed = _ensure_carstate_material_snapshots(new_data, f2004_snapshot_materials)
    visual_path = _skin_file_disk_path(game_root, entry, visual_rel)
    if visual_path and visual_path.exists() and visual_path.is_file():
        new_data, _numberplate_selector_changed = advanced_numberplate_state.sync_carfinalstate_from_visual(
            new_data,
            visual_path.read_bytes(),
        )
    local_changed = False
    if using_local_template and local_template and local_template.exists() and local_template.read_bytes() != new_data:
        _write_game_bytes(local_template, new_data, writer, label="local mechanical matrix car final state", backup=False)
        local_changed = True
    before = target.read_bytes() if target.exists() else None
    changed = before != new_data
    if changed:
        _write_game_bytes(target, new_data, writer, label="mechanical matrix car final state")
    return {
        "status": "updated" if changed or local_changed else "current",
        "path": str(target),
        "rel": game_rel_from_disk(game_root, target),
        "localPath": str(local_template) if using_local_template and local_template else "",
        "localUpdated": local_changed,
        "mechanical": mechanical_rel,
        "template": str(template),
    }


def _ext_banner_material_rel(car_content_path: str, skin_id: str) -> str:
    return norm_slashes(f"{car_content_path}\\skins\\{skin_id}\\EXT_BANNER_PRES.material")


def _ext_banner_texture_rel(car_content_path: str, skin_id: str) -> str:
    return norm_slashes(f"{car_content_path}\\skins\\{skin_id}\\EXT_Banner_C.texture")


def _skin_folder_ext_banner_material_rel(skin_folder_rel: str | None, material_name: str | None = None) -> str:
    rel = normalize_skin_asset_rel_path(str(skin_folder_rel or ""))
    raw_name = norm_slashes(str(material_name or "").strip() or "EXT_BANNER_PRES.material")
    name = Path(*split_content_path(raw_name)).name
    if not name.lower().endswith(".material"):
        name = f"{name}.material"
    return norm_slashes(f"{rel}\\{name}") if rel else ""


def _skin_folder_ext_banner_texture_rel(skin_folder_rel: str | None) -> str:
    rel = normalize_skin_asset_rel_path(str(skin_folder_rel or ""))
    return norm_slashes(f"{rel}\\EXT_Banner_C.texture") if rel else ""


def _entry_uses_advanced_banner_spatmap_fallback(entry: dict[str, Any]) -> bool:
    project_type = str(entry.get("project_type") or entry.get("projectType") or "").strip().lower()
    if project_type == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
        return False
    if any(key in entry for key in ("official_external", "official_external_role", "officialExternalRole")):
        return False
    return True


def _design_ext_banner_material_rel(path: Path | None) -> str:
    if not (path and path.exists() and path.is_file()):
        return ""
    try:
        return normalize_skin_asset_rel_path(ext_banner.design_ext_banner_material(path.read_bytes()) or "")
    except Exception:
        return ""


def _entry_source_ext_banner_material_rel(entry: dict[str, Any], game_root: Path) -> str:
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    for rel in (
        str(source_skin.get("design") or ""),
        str((entry.get("files") if isinstance(entry.get("files"), dict) else {}).get("design") or ""),
    ):
        banner_rel = _design_ext_banner_material_rel(_disk_from_game_rel(game_root, rel))
        if banner_rel:
            return banner_rel
    return ""


def _rel_is_under(rel: str, parent_rel: str) -> bool:
    rel_norm = normalize_skin_asset_rel_path(rel).lower()
    parent_norm = normalize_skin_asset_rel_path(parent_rel).lower().rstrip("\\")
    return bool(rel_norm and parent_norm and (rel_norm == parent_norm or rel_norm.startswith(parent_norm + "\\")))


def _entry_ext_banner_target_material_rel(entry: dict[str, Any], game_root: Path) -> str:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    car_content_path = str(entry.get("car_content_path") or f"content\\cars\\{entry.get('car_model') or ''}").strip()
    skin_id = str(entry.get("skin_id") or "").strip()
    skin_folder_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    if not skin_folder_rel and car_content_path and skin_id:
        skin_folder_rel = norm_slashes(f"{car_content_path}\\skins\\{skin_id}")
    if not skin_folder_rel:
        return ""

    target_design_rel = _design_ext_banner_material_rel(_local_skin_asset_path(entry, str(files.get("design") or "")))
    if not target_design_rel:
        target_design_rel = _design_ext_banner_material_rel(_disk_from_game_rel(game_root, str(files.get("design") or "")))

    source_rel = _entry_source_ext_banner_material_rel(entry, game_root)
    source_name = Path(*split_content_path(source_rel)).name if source_rel else ""
    if source_rel and "\\skins\\" in source_rel.lower() and source_name:
        return _skin_folder_ext_banner_material_rel(skin_folder_rel, source_name)
    if target_design_rel and _rel_is_under(target_design_rel, skin_folder_rel):
        return _skin_folder_ext_banner_material_rel(skin_folder_rel, Path(*split_content_path(target_design_rel)).name)
    if source_name:
        return _skin_folder_ext_banner_material_rel(skin_folder_rel, source_name)
    return _skin_folder_ext_banner_material_rel(skin_folder_rel)


def _source_ext_banner_material(root: Path, car_content_path: str, entry: dict[str, Any] | None = None) -> Path | None:
    if entry:
        source_rel = _entry_source_ext_banner_material_rel(entry, root)
        source_path = _disk_from_game_rel(root, source_rel)
        if source_path and source_path.exists() and source_path.is_file():
            return source_path
    for name in ("ext_banner.material", "EXT_BANNER.material"):
        candidate = resolve_content_path(root, f"{car_content_path}\\materials\\{name}")
        if candidate.exists() and candidate.is_file():
            return candidate
    return None


def _project_folder_from_livery_project(project: dict[str, Any]) -> Path | None:
    raw = str(project.get("projectFolder") or "").strip()
    project_path_raw = str(project.get("projectPath") or "").strip()
    if raw:
        folder = Path(raw)
        if folder.is_absolute():
            return folder
        if project_path_raw:
            project_path = Path(project_path_raw)
            if project_path.is_absolute():
                return (project_path.parent / folder).resolve()
        return folder
    if project_path_raw:
        project_path = Path(project_path_raw)
        if project_path.is_absolute():
            return project_path.parent
    return None


def _ext_banner_project_png(project: dict[str, Any]) -> Path | None:
    project_folder = _project_folder_from_livery_project(project)
    if project_folder is None:
        return None
    preferred = project_folder / preview3d.LIVE_BANNER_TEXTURE_NAME
    raw_paths = [
        project.get("liveBannerTexturePath"),
        project.get("bannerTexturePath"),
    ]
    candidates: list[Path] = []
    for raw in raw_paths:
        text = str(raw or "").strip()
        if not text:
            continue
        path = Path(text)
        candidates.append(path if path.is_absolute() else project_folder / path)
    candidates.append(preferred)
    for candidate in candidates:
        if candidate.exists() and candidate.is_file():
            return candidate
    return None


def _livery_project_banner_uv_size(
    project: dict[str, Any],
    game_root: Path,
    entry: dict[str, Any],
    skin_folder: Path | None,
    *,
    local_only: bool = False,
) -> dict[str, Any]:
    fallback = {"width": 4096, "height": 1024, "source": "fallback", "path": ""}
    png = _ext_banner_project_png(project)
    if png and png.exists() and not _is_preview_placeholder_png(png):
        try:
            from PIL import Image

            with Image.open(png) as image:
                width, height = image.size
            if width > 1 and height > 1:
                return {"width": int(width), "height": int(height), "source": "projectPng", "path": str(png)}
        except Exception:
            pass
    texture = _find_banner_texture_for_skin(
        game_root,
        entry,
        skin_folder,
        local_only=local_only,
        allow_spatmap_fallback=_entry_uses_advanced_banner_spatmap_fallback(entry),
    )
    if texture and texture.exists():
        try:
            desc = parse_texture_descriptor(texture)
            width = int(desc.get("width") or 0)
            height = int(desc.get("height") or 0)
            if width > 1 and height > 1:
                return {"width": width, "height": height, "source": "textureDescriptor", "path": str(texture)}
        except Exception:
            pass
    return fallback


def _set_texture_descriptor_png_source(texture_path: Path, source_png_ref: str, writer: GameFileWriter | None = None) -> bool:
    if not texture_path.exists() or not source_png_ref:
        return False
    old_data = texture_path.read_bytes()
    replacements: dict[str, str] = {}
    for _proto_path, text in collect_proto_strings(old_data):
        if isinstance(text, str) and text.lower().endswith(".png"):
            replacements[_normalize_exact_text(text)] = norm_slashes(source_png_ref)
    if not replacements:
        return False
    new_data = patch_strings_recursive_exact(old_data, replacements)
    if new_data == old_data:
        return False
    _write_game_bytes(texture_path, new_data, writer, label="texture descriptor source")
    return True


def _ext_banner_texture_template_candidates(
    entry: dict[str, Any],
    game_root: Path,
    target_skin_folder: Path | None,
    *,
    include_target: bool = True,
    include_spatmap_fallback: bool = False,
) -> list[Path]:
    car_content_path = str(entry.get("car_content_path") or f"content\\cars\\{entry.get('car_model') or ''}").strip()
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    candidates: list[Path] = []

    def add(path: Path | None) -> None:
        if not path or not path.exists() or path.suffix.lower() != ".texture":
            return
        key = str(path.resolve()).lower()
        if key not in {str(row.resolve()).lower() for row in candidates}:
            candidates.append(path)

    if include_target and target_skin_folder:
        add(_find_texture_by_stem(target_skin_folder, "ext_banner_c"))
    if project_types.project_type_for_entry(entry) == project_types.PROJECT_TYPE_ADVANCED_EXTRACTED_CONTENT:
        for path in _advanced_ext_banner_source_material_texture_candidates(entry, game_root):
            add(path)
    source_skin_rel = normalize_skin_asset_rel_path(str(source_skin.get("skin_folder") or ""))
    add(_find_texture_by_stem(_disk_from_game_rel(game_root, source_skin_rel), "ext_banner_c") if source_skin_rel else None)
    skin_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    if include_target:
        add(_find_texture_by_stem(_disk_from_game_rel(game_root, skin_rel), "ext_banner_c") if skin_rel else None)
    if car_content_path:
        add(resolve_content_path(game_root, f"{car_content_path}\\textures\\ext_banner_c.texture"))
        car_skins = resolve_content_path(game_root, f"{car_content_path}\\skins")
        if car_skins.exists():
            try:
                for path in sorted(car_skins.glob("*\\ext_banner_c.texture"), key=lambda row: str(row).lower()):
                    add(path)
            except Exception:
                pass
    if include_spatmap_fallback:
        for path in _ext_banner_spatmap_texture_candidates(entry, game_root):
            add(path)
    return candidates


def _advanced_ext_banner_source_material_texture_candidates(
    entry: dict[str, Any],
    game_root: Path,
) -> list[Path]:
    """Resolve Advanced banner templates even when a car reuses another car's material."""
    if project_types.project_type_for_entry(entry) != project_types.PROJECT_TYPE_ADVANCED_EXTRACTED_CONTENT:
        return []
    car_content_path = str(entry.get("car_content_path") or f"content\\cars\\{entry.get('car_model') or ''}").strip()
    material = _source_ext_banner_material(game_root, car_content_path, entry)
    if material is None or not material.exists():
        return []
    try:
        material_info = inspect_material(material, root=game_root)
    except Exception:
        return []
    candidates: list[Path] = []
    seen: set[str] = set()
    for slot in material_info.get("texture_slots", []):
        if str(slot.get("name") or "") != "Base_BaseColorMap":
            continue
        value = str(slot.get("value") or "").strip()
        resolved_text = str(slot.get("resolved_path") or "").strip()
        resolved = Path(resolved_text) if resolved_text else (
            material_resolve_ref(game_root, value) or resolve_content_path(game_root, value)
        )
        if not resolved or not resolved.exists() or resolved.suffix.lower() != ".texture":
            continue
        try:
            key = str(resolved.resolve()).lower()
        except Exception:
            key = str(resolved).lower()
        if key in seen:
            continue
        seen.add(key)
        candidates.append(resolved)
    return candidates


def _ext_banner_spatmap_texture_candidates(entry: dict[str, Any], game_root: Path) -> list[Path]:
    design = _find_design_for_skin(game_root, entry, local_only=False)
    if not design or not design.exists():
        return []
    try:
        slots = extract_design_slot_materials(design.read_bytes())
    except Exception:
        return []
    candidates: list[Path] = []
    seen: set[str] = set()
    for material_rel in slots.get("EXT_BANNER", []):
        material = find_material_file(game_root, str(material_rel or ""))
        if not material or not material.exists():
            continue
        try:
            material_info = inspect_material(material, root=game_root)
        except Exception:
            continue
        for slot in material_info.get("texture_slots", []):
            if str(slot.get("name") or "") != "SpatMap":
                continue
            value = str(slot.get("value") or "")
            if ".texture" not in value.lower() or "default_material" in value.lower():
                continue
            resolved_text = str(slot.get("resolved_path") or "").strip()
            resolved = Path(resolved_text) if resolved_text else (material_resolve_ref(game_root, value) or resolve_content_path(game_root, value))
            if not resolved or not resolved.exists() or resolved.suffix.lower() != ".texture":
                continue
            if "banner" not in resolved.name.lower():
                continue
            try:
                key = str(resolved.resolve()).lower()
            except Exception:
                key = str(resolved).lower()
            if key in seen:
                continue
            seen.add(key)
            candidates.append(resolved)
    return candidates


def _texture_descriptor_png_source(texture_path: Path | None) -> str:
    if not texture_path or not texture_path.exists() or not texture_path.is_file():
        return ""
    try:
        for _proto_path, text in collect_proto_strings(texture_path.read_bytes()):
            if isinstance(text, str) and text.lower().endswith(".png"):
                return text
    except Exception:
        return ""
    return ""


def _texture_descriptor_source_is_generated_ref(source: str) -> bool:
    value = norm_slashes(str(source or "")).strip().lower()
    return bool(
        value.startswith("content\\cars\\")
        or value.startswith("liverylab\\")
        or value.startswith("release\\")
        or value.startswith("liveryprojects\\")
    )


def _repair_ext_banner_texture_descriptor_source(
    entry: dict[str, Any],
    game_root: Path,
    skin_folder: Path | None,
    texture_path: Path,
    writer: GameFileWriter | None,
) -> bool:
    current_source = _texture_descriptor_png_source(texture_path)
    if current_source and not _texture_descriptor_source_is_generated_ref(current_source):
        return False
    try:
        target_key = str(texture_path.resolve()).lower()
    except Exception:
        target_key = str(texture_path).lower()
    for candidate in _ext_banner_texture_template_candidates(entry, game_root, skin_folder, include_target=False):
        try:
            if str(candidate.resolve()).lower() == target_key:
                continue
        except Exception:
            pass
        source = _texture_descriptor_png_source(candidate)
        if not source or _texture_descriptor_source_is_generated_ref(source):
            continue
        return _set_texture_descriptor_png_source(texture_path, source, writer)
    return False


EXT_BANNER_NEUTRAL_TEXTURE_SLOTS: dict[str, tuple[str, tuple[int, int, int, int]]] = {
    "SpatMap": ("ext_banner_d", (0, 0, 0, 255)),
    "Base_MetalnessMap": ("ext_banner_m", (0, 0, 0, 255)),
    "Base_RoughnessMap": ("ext_banner_r", (190, 190, 190, 255)),
    "Base_ClearCoatMap": ("ext_banner_cc", (0, 0, 0, 255)),
    "Base_ClearCoatRoughnessMap": ("ext_banner_cr", (255, 255, 255, 255)),
}


def _same_disk_path(left: Path | None, right: Path | None) -> bool:
    if left is None or right is None:
        return False
    try:
        return str(left.resolve()).lower() == str(right.resolve()).lower()
    except Exception:
        return str(left).lower() == str(right).lower()


def _ext_banner_slot_texture_template(
    game_root: Path,
    write_root: Path,
    skin_folder_rel: str,
    source_texture: Path,
    stem_hint: str,
    excluded_paths: list[Path] | None = None,
) -> Path | None:
    names = [
        f"{stem_hint}.texture",
        f"{stem_hint.upper()}.texture",
        f"EXT_Banner_{stem_hint.rsplit('_', 1)[-1].upper()}.texture",
        f"ext_banner_{stem_hint.rsplit('_', 1)[-1]}.texture",
    ]
    candidates: list[Path] = []
    if source_texture.stem.lower() == stem_hint.lower():
        candidates.append(source_texture)
    candidates.extend(source_texture.parent / name for name in names)
    skin_rel = normalize_skin_asset_rel_path(skin_folder_rel)
    if skin_rel:
        candidates.extend(write_root / Path(*split_content_path(norm_slashes(f"{skin_rel}\\{name}"))) for name in names)
        if "\\skins\\" in skin_rel.lower():
            car_content_rel = re.split(r"\\skins\\", skin_rel, maxsplit=1, flags=re.I)[0]
            candidates.extend(game_root / Path(*split_content_path(norm_slashes(f"{car_content_rel}\\textures\\{name}"))) for name in names)
            candidates.extend(write_root / Path(*split_content_path(norm_slashes(f"{car_content_rel}\\textures\\{name}"))) for name in names)
    seen: set[str] = set()
    excluded: set[str] = set()
    for path in excluded_paths or []:
        try:
            excluded.add(str(path.resolve()).lower())
        except Exception:
            excluded.add(str(path).lower())
    for candidate in candidates:
        try:
            key = str(candidate.resolve()).lower()
        except Exception:
            key = str(candidate).lower()
        if key in seen or key in excluded:
            continue
        seen.add(key)
        if candidate.exists() and candidate.is_file():
            return candidate
    return None


def _texture_descriptor_matches_mips(texture_path: Path, mips_path: Path, slot_name: str) -> bool:
    if not texture_path.exists() or not mips_path.exists():
        return False
    try:
        desc = parse_texture_descriptor(texture_path)
        choose_auto_format(texture_path, desc, mips_path.stat().st_size, slot_name)
        return True
    except Exception:
        return False


def _ext_banner_descriptor_source_for_mips(
    target_texture: Path,
    reference_mips: Path,
    slot_name: str,
    candidates: list[Path],
) -> Path | None:
    if _texture_descriptor_matches_mips(target_texture, reference_mips, slot_name):
        return target_texture
    for candidate in candidates:
        if candidate and _texture_descriptor_matches_mips(candidate, reference_mips, slot_name):
            return candidate
    return None


def _solid_texturemips_from_rgba(
    texture_path: Path,
    reference_mips_path: Path,
    slot_name: str,
    rgba: tuple[int, int, int, int],
) -> tuple[bytes, str, str]:
    desc = parse_texture_descriptor(texture_path)
    format_name, auto_note = choose_auto_format(texture_path, desc, reference_mips_path.stat().st_size, slot_name)
    tmp_dir = create_short_work_temp_dir("ebn")
    try:
        try:
            from PIL import Image
        except Exception as exc:
            raise RuntimeError(f"Pillow is required to create neutral texturemips: {exc}") from exc
        source_png = tmp_dir / "neutral.png"
        dds_path = tmp_dir / "neutral.dds"
        Image.new("RGBA", (int(desc["width"]), int(desc["height"])), rgba).save(source_png)
        convert_image_to_dds_with_texconv(
            source_png,
            dds_path,
            format_name,
            None,
            int(desc["mips"]),
            (int(desc["width"]), int(desc["height"])),
        )
        return texturemips_bytes_from_dds(texture_path, dds_path), format_name, auto_note
    finally:
        cleanup_short_work_temp_dir(tmp_dir)


def _ensure_ext_banner_neutral_texture_maps(
    material_path: Path,
    game_root: Path,
    write_root: Path,
    skin_folder_rel: str,
    skin_id: str,
    writer: GameFileWriter | None,
) -> dict[str, Any]:
    if not material_path.exists():
        return {"changed": False, "reason": "material missing"}
    material_info = inspect_material(material_path)
    replacements: dict[str, str] = {}
    neutralized: list[dict[str, Any]] = []
    copied = 0
    written = 0
    for slot in material_info.get("texture_slots", []):
        slot_name = str(slot.get("name") or "")
        neutral = EXT_BANNER_NEUTRAL_TEXTURE_SLOTS.get(slot_name)
        texture_rel = slot.get("value")
        if neutral is None or not isinstance(texture_rel, str) or ".texture" not in texture_rel.lower():
            continue
        normalized_texture = norm_slashes(texture_rel)
        if "banner" not in Path(normalized_texture.replace("\\", "/")).name.lower():
            continue
        stem_hint, rgba = neutral
        source_texture = material_resolve_ref(game_root, normalized_texture) or resolve_content_path(game_root, normalized_texture)
        if not source_texture.exists():
            source_texture = write_root / Path(*split_content_path(normalized_texture))
        if not source_texture.exists():
            continue
        source_mips = source_texture.with_suffix(".texturemips")
        target_name = source_texture.name
        if Path(target_name).stem.lower() == "ext_banner_c":
            target_name = f"{stem_hint}.texture"
        target_rel = norm_slashes(f"{skin_folder_rel}\\{target_name}")
        target_texture = write_root / Path(*split_content_path(target_rel))
        target_mips = target_texture.with_suffix(".texturemips")
        template_texture = _ext_banner_slot_texture_template(game_root, write_root, skin_folder_rel, source_texture, stem_hint)
        template_mips = template_texture.with_suffix(".texturemips") if template_texture else None
        if target_mips.exists() and _texture_descriptor_matches_mips(target_texture, target_mips, slot_name):
            reference_mips = target_mips
        else:
            reference_mips = template_mips if template_mips and template_mips.exists() else (source_mips if source_mips.exists() else target_mips)
        if not reference_mips.exists():
            continue
        descriptor_candidates = [
            candidate
            for candidate in (template_texture, source_texture)
            if candidate is not None and candidate.exists()
        ]
        if not target_texture.exists():
            descriptor_source = _ext_banner_descriptor_source_for_mips(target_texture, reference_mips, slot_name, descriptor_candidates)
            if descriptor_source is None:
                continue
            _copy_game_file(descriptor_source, target_texture, writer, label="EXT_BANNER neutral texture descriptor", backup=True)
            localize_texture_descriptor_source(target_texture, skin_id, writer=writer)
            copied += 1
        elif not _texture_descriptor_matches_mips(target_texture, reference_mips, slot_name):
            repair_template = _ext_banner_slot_texture_template(
                game_root,
                write_root,
                skin_folder_rel,
                source_texture,
                stem_hint,
                excluded_paths=[target_texture],
            )
            if repair_template and not _same_disk_path(repair_template, target_texture) and _texture_descriptor_matches_mips(repair_template, reference_mips, slot_name):
                _copy_game_file(repair_template, target_texture, writer, label="EXT_BANNER neutral texture descriptor repair", backup=True)
                localize_texture_descriptor_source(target_texture, skin_id, writer=writer)
                copied += 1
            elif descriptor_source := _ext_banner_descriptor_source_for_mips(target_texture, reference_mips, slot_name, descriptor_candidates):
                if not _same_disk_path(descriptor_source, target_texture):
                    _copy_game_file(descriptor_source, target_texture, writer, label="EXT_BANNER neutral texture descriptor repair", backup=True)
                    localize_texture_descriptor_source(target_texture, skin_id, writer=writer)
                    copied += 1
        if not _texture_descriptor_matches_mips(target_texture, reference_mips, slot_name):
            continue
        texturemips_data, format_name, note = _solid_texturemips_from_rgba(target_texture, reference_mips, slot_name, rgba)
        if not target_mips.exists() or target_mips.read_bytes() != texturemips_data:
            _write_game_bytes(target_mips, texturemips_data, writer, label="EXT_BANNER neutral texturemips")
            written += 1
        replacements[slot_name] = target_rel
        neutralized.append({
            "slot": slot_name,
            "texture": str(target_texture),
            "textureRel": target_rel,
            "format": format_name,
            "note": note,
        })

    if not replacements:
        return {"changed": False, "neutralized": [], "copied": copied, "written": written}
    old_material = material_path.read_bytes()
    new_material = _set_material_texture_slots_nested(old_material, replacements)
    flag_params = {
        name
        for name, _stem_rgba in EXT_BANNER_NEUTRAL_TEXTURE_SLOTS.items()
        if name in replacements and name.startswith("Base_") and name.endswith("Map")
    }
    has_params = {name.replace("Base_", "Base_Has") for name in flag_params}
    if has_params:
        new_material = _set_material_float_parameters(new_material, has_params, 1.0)
        new_material = _ensure_material_override_flags(new_material, 21, has_params, 1)
    new_material = _ensure_material_override_flags(new_material, 22, set(replacements), 1)
    material_changed = new_material != old_material
    if material_changed:
        _write_game_bytes(material_path, new_material, writer, label="EXT_BANNER neutral material maps")
    return {
        "changed": bool(material_changed or copied or written),
        "materialChanged": material_changed,
        "neutralized": neutralized,
        "copied": copied,
        "written": written,
    }


def _ensure_ext_banner_project_texture_for_skin(
    entry: dict[str, Any],
    game_root: Path,
    write_root: Path,
    skin_folder: Path | None,
    project: dict[str, Any],
    writer: GameFileWriter | None = None,
    *,
    only_if_newer: bool = False,
) -> dict[str, Any]:
    item: dict[str, Any] = {
        "label": "EXT_Banner_C",
        "stem": "ext_banner_c",
        "status": "pending",
    }
    png = _ext_banner_project_png(project)
    item["png"] = str(png or "")
    if png is None or not png.exists() or _is_preview_placeholder_png(png):
        item.update({"status": "skipped", "reason": "optional project PNG not found"})
        return item

    car_content_path = str(entry.get("car_content_path") or f"content\\cars\\{entry.get('car_model') or ''}").strip()
    skin_id = str(entry.get("skin_id") or "").strip()
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_folder_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    if not skin_folder_rel and car_content_path and skin_id:
        skin_folder_rel = norm_slashes(f"{car_content_path}\\skins\\{skin_id}")
    if not skin_folder_rel or not skin_id:
        item.update({"status": "skipped", "reason": "skin folder metadata missing"})
        return item
    if skin_folder is None:
        skin_folder = write_root / Path(*split_content_path(skin_folder_rel))
    skin_folder.mkdir(parents=True, exist_ok=True)

    material_rel = _entry_ext_banner_target_material_rel(entry, game_root)
    if not material_rel:
        material_rel = _skin_folder_ext_banner_material_rel(skin_folder_rel)
    texture_rel = _skin_folder_ext_banner_texture_rel(skin_folder_rel)
    material_path = write_root / Path(*split_content_path(material_rel))
    texture_path = write_root / Path(*split_content_path(texture_rel))
    mips_path = texture_path.with_suffix(".texturemips")
    item.update({"material": str(material_path), "texture": str(texture_path), "textureRel": texture_rel})

    copied = 0
    patched = 0
    source_material = _source_ext_banner_material(game_root, car_content_path, entry)
    if source_material is None:
        item.update({"status": "skipped", "reason": "source EXT_BANNER material missing"})
        return item
    template_textures: list[Path] = []
    template_texture = None
    use_spatmap_fallback = _entry_uses_advanced_banner_spatmap_fallback(entry)
    for candidate in _ext_banner_texture_template_candidates(entry, game_root, skin_folder, include_spatmap_fallback=use_spatmap_fallback):
        if candidate.with_suffix(".texturemips").exists():
            template_textures.append(candidate)
            template_texture = candidate
            if len(template_textures) == 1:
                break
    if template_texture is not None:
        for candidate in _ext_banner_texture_template_candidates(entry, game_root, skin_folder, include_spatmap_fallback=use_spatmap_fallback):
            if candidate in template_textures or not candidate.with_suffix(".texturemips").exists():
                continue
            template_textures.append(candidate)
    if (not texture_path.exists() or not mips_path.exists()) and template_texture is None:
        item.update({"status": "skipped", "reason": "EXT_BANNER texture template missing"})
        return item

    material_seed = source_material.read_bytes()
    old_material = material_path.read_bytes() if material_path.exists() else None
    if old_material is None or old_material == b"":
        _copy_game_file(source_material, material_path, writer, label="skin-local EXT_BANNER material", backup=True)
        copied += 1
        old_material = material_seed
    new_material = _set_material_texture_slots_nested(old_material, {"Base_BaseColorMap": texture_rel})
    new_material = _set_material_float_parameters(new_material, {"Base_HasBaseColorMap"}, 1.0)
    new_material = _ensure_material_override_flags(new_material, 21, {"Base_HasBaseColorMap"}, 1)
    new_material = _ensure_material_override_flags(new_material, 22, {"Base_BaseColorMap"}, 1)
    if new_material != old_material:
        _write_game_bytes(material_path, new_material, writer, label="EXT_BANNER base color")
        patched += 1
    neutral_maps = _ensure_ext_banner_neutral_texture_maps(
        material_path,
        game_root,
        write_root,
        skin_folder_rel,
        skin_id,
        writer,
    )
    if neutral_maps.get("changed"):
        patched += 1
    item["neutralMaps"] = neutral_maps

    if not texture_path.exists():
        if template_texture is None:
            item.update({"status": "skipped", "reason": "EXT_BANNER texture template missing"})
            return item
        source_mips = template_texture.with_suffix(".texturemips")
        descriptor_source = _ext_banner_descriptor_source_for_mips(texture_path, source_mips, "Base_BaseColorMap", template_textures)
        if descriptor_source is None:
            item.update({"status": "skipped", "reason": "EXT_BANNER texture template does not match texturemips"})
            return item
        _copy_game_file(descriptor_source, texture_path, writer, label="EXT_BANNER texture descriptor", backup=True)
        copied += 1
    if _repair_ext_banner_texture_descriptor_source(entry, game_root, skin_folder, texture_path, writer):
        patched += 1
    if not mips_path.exists():
        source_mips = template_texture.with_suffix(".texturemips") if template_texture else None
        if source_mips is None or not source_mips.exists():
            item.update({"status": "skipped", "reason": "EXT_BANNER texturemips template missing"})
            return item
        _copy_game_file(source_mips, mips_path, writer, label="EXT_BANNER texturemips seed", backup=True)
        copied += 1
    descriptor_source = _ext_banner_descriptor_source_for_mips(texture_path, mips_path, "Base_BaseColorMap", template_textures)
    if descriptor_source is None:
        template_pair = next(
            (
                candidate
                for candidate in template_textures
                if _texture_descriptor_matches_mips(candidate, candidate.with_suffix(".texturemips"), "Base_BaseColorMap")
            ),
            None,
        )
        if template_pair is None:
            item.update({"status": "skipped", "reason": "EXT_BANNER descriptor does not match texturemips"})
            return item
        if not _same_disk_path(template_pair, texture_path):
            _copy_game_file(template_pair, texture_path, writer, label="EXT_BANNER texture descriptor repair", backup=True)
            localize_texture_descriptor_source(texture_path, skin_id, writer=writer)
            patched += 1
        _copy_game_file(template_pair.with_suffix(".texturemips"), mips_path, writer, label="EXT_BANNER texturemips seed repair", backup=True)
        copied += 1
    elif not _same_disk_path(descriptor_source, texture_path):
        _copy_game_file(descriptor_source, texture_path, writer, label="EXT_BANNER texture descriptor repair", backup=True)
        localize_texture_descriptor_source(texture_path, skin_id, writer=writer)
        patched += 1
    if only_if_newer and mips_path.exists() and png.stat().st_mtime <= mips_path.stat().st_mtime + 0.001:
        status = "updated" if copied or patched else "skipped"
        item.update({
            "status": status,
            "reason": "texturemips is current" if status == "skipped" else "assets prepared",
            "texturemips": str(mips_path),
            "copied": copied,
            "patched": patched,
        })
        return item

    texconv = find_texconv_executable()
    if texconv is None:
        raise RuntimeError("texconv.exe was not found. Place it in LiveryLab/tools or configure it in Settings.")
    desc = parse_texture_descriptor(texture_path)
    format_name, auto_note = choose_auto_format(texture_path, desc, mips_path.stat().st_size, "Base_BaseColorMap")
    tmp_dir = create_short_work_temp_dir("eb")
    try:
        dds_path = tmp_dir / "b.dds"
        convert_image_to_dds_with_texconv(
            png,
            dds_path,
            format_name,
            texconv,
            int(desc["mips"]),
            (int(desc["width"]), int(desc["height"])),
        )
        texturemips_data = texturemips_bytes_from_dds(texture_path, dds_path)
        if writer is None:
            backup = safe_write_bytes(mips_path, texturemips_data)
            write_result = {
                "action": "write_bytes",
                "target": str(mips_path),
                "source": str(png),
                "label": "EXT_Banner_C",
                "status": "written",
                "backup": str(backup) if backup else "",
                "bytes_written": len(texturemips_data),
            }
        else:
            writer_result = writer.write_bytes(mips_path, texturemips_data, label="EXT_Banner_C", backup=True)
            write_result = writer_result.to_dict()
        item.update({
            "status": "updated",
            "reason": "converted",
            "texturemips": str(mips_path),
            "format": format_name,
            "note": auto_note,
            "mips": int(desc["mips"]),
            "width": int(desc["width"]),
            "height": int(desc["height"]),
            "bytes": len(texturemips_data),
            "copied": copied,
            "patched": patched,
            "writeResult": write_result,
        })
        return item
    finally:
        cleanup_short_work_temp_dir(tmp_dir)


def _entry_references_content_path(entry: dict[str, Any], target_rel: str) -> bool:
    target_key = normalize_skin_asset_rel_path(target_rel).lower()
    if not target_key:
        return False
    for _kind, path in _activation_reference_scan_files(entry):
        try:
            strings = collect_proto_strings(path.read_bytes())
        except Exception:
            continue
        for _proto_path, text in strings:
            for _start, ref in _extract_content_references(text):
                if normalize_skin_asset_rel_path(ref).lower() == target_key:
                    return True
    return False


def repair_advanced_ext_banner_runtime_dependencies(
    entry: dict[str, Any],
    game_root: Path,
) -> dict[str, Any]:
    """Repair a generated Advanced banner pair locally before validation or export."""
    if project_types.project_type_for_entry(entry) != project_types.PROJECT_TYPE_ADVANCED_EXTRACTED_CONTENT:
        return {"applicable": False, "required": False, "ready": True, "reason": "not an Advanced project"}
    local_root = _entry_local_root(entry)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_folder_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    skin_id = str(entry.get("skin_id") or "").strip()
    if local_root is None or not skin_folder_rel or not skin_id:
        return {"applicable": True, "required": False, "ready": True, "reason": "local skin metadata missing"}
    skin_folder = local_root / Path(*split_content_path(skin_folder_rel))
    texture_rel = _skin_folder_ext_banner_texture_rel(skin_folder_rel)
    texture_path = local_root / Path(*split_content_path(texture_rel))
    mips_path = texture_path.with_suffix(".texturemips")
    referenced = _entry_references_content_path(entry, texture_rel)
    pair_ready = (
        texture_path.exists()
        and mips_path.exists()
        and _texture_descriptor_matches_mips(texture_path, mips_path, "Base_BaseColorMap")
    )
    if not referenced or pair_ready:
        return {
            "applicable": True,
            "required": False,
            "ready": True,
            "reason": "banner runtime pair is current" if pair_ready else "local banner runtime pair is not referenced",
            "texture": str(texture_path),
            "texturemips": str(mips_path),
        }

    prepared = prepare_livery_preview_project(str(entry.get("key") or ""), game_root)
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    png = _ext_banner_project_png(project)
    if png is None or not png.exists() or _is_preview_placeholder_png(png):
        return {
            "applicable": True,
            "required": True,
            "ready": False,
            "reason": "EXT_Banner_C.png is missing from the Advanced LiveryProject",
            "texture": str(texture_path),
            "texturemips": str(mips_path),
        }
    result = _ensure_ext_banner_project_texture_for_skin(
        entry,
        game_root,
        local_root,
        skin_folder,
        project,
        writer=None,
        only_if_newer=False,
    )
    pair_ready = (
        texture_path.exists()
        and mips_path.exists()
        and _texture_descriptor_matches_mips(texture_path, mips_path, "Base_BaseColorMap")
    )
    return {
        "applicable": True,
        "required": True,
        "ready": pair_ready,
        "changed": pair_ready and str(result.get("status") or "") == "updated",
        "reason": "Advanced banner runtime pair repaired" if pair_ready else str(result.get("reason") or "banner runtime repair failed"),
        "texture": str(texture_path),
        "texturemips": str(mips_path),
        "png": str(png),
        "result": result,
    }


def _entry_ext_banner_material_rel(entry: dict[str, Any], game_root: Path) -> str:
    material_rel = _entry_ext_banner_target_material_rel(entry, game_root)
    if not material_rel:
        return ""
    local_material = _local_skin_asset_path(entry, material_rel)
    game_material = _disk_from_game_rel(game_root, material_rel)
    if (local_material and local_material.exists()) or (game_material and game_material.exists()):
        return material_rel
    return ""


def _entry_ext_banner_label(entry: dict[str, Any], game_root: Path) -> str:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    design_refs = [
        normalize_skin_asset_rel_path(str(files.get("design") or "")),
        normalize_skin_asset_rel_path(str(source_skin.get("design") or "")),
    ]
    seen: set[str] = set()
    for rel in design_refs:
        if not rel:
            continue
        for path in (_local_skin_asset_path(entry, rel), _disk_from_game_rel(game_root, rel)):
            if not (path and path.exists() and path.is_file()):
                continue
            key = str(path.resolve()).lower()
            if key in seen:
                continue
            seen.add(key)
            label = ext_banner.design_ext_banner_label(path.read_bytes())
            if label:
                return label

    finalstate_refs: list[str] = []
    for _kind, rel_norm in _file_rel_values(files, {"carfinalstate", "carfinalstates"}):
        finalstate_refs.append(rel_norm)
    source_finalstate = normalize_skin_asset_rel_path(str(source_skin.get("carfinalstate") or ""))
    if source_finalstate:
        finalstate_refs.append(source_finalstate)
    for rel in finalstate_refs:
        for path in (_local_skin_asset_path(entry, rel), _disk_from_game_rel(game_root, rel)):
            if not (path and path.exists() and path.is_file()):
                continue
            key = str(path.resolve()).lower()
            if key in seen:
                continue
            seen.add(key)
            label = ext_banner.carstate_ext_banner_label(path.read_bytes())
            if label:
                return label

    return ext_banner.EXT_BANNER_LABEL


def _ensure_ext_banner_for_game_entry(
    entry: dict[str, Any],
    game_root: Path,
    visual_rel: str,
    mechanical_rels: Iterable[str],
    writer: GameFileWriter | None,
    *,
    include_savedcars: bool = True,
) -> dict[str, Any]:
    material_rel = _entry_ext_banner_material_rel(entry, game_root)
    if not material_rel:
        return {"changed": False, "skipped": "skin-local EXT_BANNER material missing"}
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    changed = False
    design_changed = False
    visual_removed = False
    finalstate_changed = 0
    finalstate_current = 0
    local_finalstate_changed = 0
    local_finalstate_current = 0
    savedcar_changed = 0
    savedcar_current = 0
    savedcar_errors: list[str] = []
    label = _entry_ext_banner_label(entry, game_root)

    design_rel = str(files.get("design") or "")
    design_targets = [
        (_local_skin_asset_path(entry, design_rel), True),
        (_disk_from_game_rel(game_root, design_rel), False),
    ]
    design_seen: set[str] = set()
    for design_path, is_local in design_targets:
        if not (design_path and design_path.exists() and design_path.is_file()):
            continue
        key = str(design_path.resolve()).lower()
        if key in design_seen:
            continue
        design_seen.add(key)
        old_data = design_path.read_bytes()
        new_data, did_change = ext_banner.ensure_design_ext_banner_slot(old_data, material_rel, label)
        if did_change:
            if is_local:
                safe_write_bytes(design_path, new_data)
            else:
                _write_game_bytes(design_path, new_data, writer, label="EXT_BANNER design slot")
            changed = True
            design_changed = True

    visual_path = _disk_from_game_rel(game_root, visual_rel or str(files.get("visual_preset") or ""))
    if visual_path and visual_path.exists():
        old_data = visual_path.read_bytes()
        new_data, did_change = ext_banner.remove_visualpreset_ext_banner_entry(old_data)
        if did_change:
            _write_game_bytes(visual_path, new_data, writer, label="stale EXT_BANNER visual preset cleanup")
            changed = True
            visual_removed = True

    finalstate_paths: list[tuple[Path, bool]] = []
    seen: set[str] = set()

    def add_finalstate(path: Path | None, is_local: bool = False) -> None:
        if not (path and path.exists() and path.is_file()):
            return
        key = str(path.resolve()).lower()
        if key in seen:
            return
        seen.add(key)
        finalstate_paths.append((path, is_local))

    for _kind, rel_norm in _file_rel_values(files, {"carfinalstate", "carfinalstates"}):
        add_finalstate(_local_skin_asset_path(entry, rel_norm), True)
        add_finalstate(_disk_from_game_rel(game_root, rel_norm))
    visual_refs = [visual_rel or str(files.get("visual_preset") or "")]
    for path in _carfinalstate_paths_for_visual_refs(game_root, visual_refs, mechanical_rels):
        add_finalstate(path)

    for path, is_local in finalstate_paths:
        old_data = path.read_bytes()
        new_data, did_change = ext_banner.ensure_carstate_ext_banner_snapshot(old_data, material_rel, label)
        if did_change:
            if is_local:
                safe_write_bytes(path, new_data)
                local_finalstate_changed += 1
            else:
                _write_game_bytes(path, new_data, writer, label="EXT_BANNER car final state")
                finalstate_changed += 1
            changed = True
        else:
            if is_local:
                local_finalstate_current += 1
            else:
                finalstate_current += 1

    savedcar_paths: list[Path] = []
    if include_savedcars:
        savedcar_seen: set[str] = set()
        scan_refs = [visual_rel or str(files.get("visual_preset") or "")]
        for ref in scan_refs:
            if not str(ref or "").strip():
                continue
            hits, errors = _scan_savedcars_for_path(str(ref))
            savedcar_errors.extend(errors)
            for path in hits:
                key = str(path.resolve()).lower()
                if key in savedcar_seen:
                    continue
                savedcar_seen.add(key)
                savedcar_paths.append(path)

    for path in savedcar_paths:
        try:
            old_data = path.read_bytes()
            new_data, did_change = ext_banner.ensure_carstate_ext_banner_snapshot(old_data, material_rel, label)
            if did_change:
                _write_game_bytes(path, new_data, writer, label="EXT_BANNER saved car state")
                changed = True
                savedcar_changed += 1
            else:
                savedcar_current += 1
        except Exception as exc:
            savedcar_errors.append(f"{path}: {exc}")

    return {
        "changed": changed,
        "material": material_rel,
        "label": label,
        "designChanged": design_changed,
        "visualRemoved": visual_removed,
        "finalstateChanged": finalstate_changed,
        "finalstateCurrent": finalstate_current,
        "finalstateCount": len(finalstate_paths),
        "localFinalstateChanged": local_finalstate_changed,
        "localFinalstateCurrent": local_finalstate_current,
        "savedcarChanged": savedcar_changed,
        "savedcarCurrent": savedcar_current,
        "savedcarCount": len(savedcar_paths),
        "savedcarErrors": savedcar_errors,
    }


def _thumbnail_png_source_for_game_sync(entry: dict[str, Any]) -> Path | None:
    if skin_policy.policy_for_skin(entry).get("readOnly"):
        override = _project_skin_thumbnail_path_for_entry(entry)
        if override.exists() and override.is_file():
            return override
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    local_png = _local_skin_asset_path(entry, str(files.get("thumbnail_png") or ""))
    if local_png and local_png.exists() and local_png.is_file():
        return local_png
    override = _project_skin_thumbnail_path_for_entry(entry)
    return override if override.exists() and override.is_file() else None


def _write_skin_thumbnail_for_mechanical(
    entry: dict[str, Any],
    game_root: Path,
    mechanical_rel: str,
    visual_rel: str,
    writer: GameFileWriter,
) -> dict[str, Any]:
    local_png = _thumbnail_png_source_for_game_sync(entry)
    if not (local_png and local_png.exists()):
        sync_livery_project_thumbnail_to_local_skin(entry)
        local_png = _thumbnail_png_source_for_game_sync(entry)
    if not (local_png and local_png.exists()):
        return {"status": "skipped", "reason": "local thumbnail png missing", "mechanical": mechanical_rel}
    template = find_any_generated_thumbnail_template(game_root, str(entry.get("car_content_path") or ""))
    if template is None:
        return {"status": "skipped", "reason": "usable thumbnail template missing", "mechanical": mechanical_rel}
    target = generated_thumbnail_disk_path(game_root, str(entry.get("car_content_path") or ""), mechanical_rel, visual_rel)
    before_texture = target.read_bytes() if target.exists() else None
    before_mips = target.with_suffix(".texturemips").read_bytes() if target.with_suffix(".texturemips").exists() else None
    try:
        texture_path, mips_path = write_generated_thumbnail_from_preview(template, target, local_png, overwrite=True, writer=writer)
    except RuntimeError as exc:
        if is_optional_thumbnail_generation_error(exc):
            return {"status": "skipped", "reason": f"thumbnail conversion unsupported: {exc}", "mechanical": mechanical_rel}
        raise
    changed = True
    try:
        changed = before_texture != texture_path.read_bytes() or before_mips != mips_path.read_bytes()
    except Exception:
        changed = True
    return {
        "status": "updated" if changed else "current",
        "path": str(texture_path),
        "mips": str(mips_path),
        "rel": game_rel_from_disk(game_root, texture_path),
        "mipsRel": game_rel_from_disk(game_root, mips_path),
        "mechanical": mechanical_rel,
    }


def sync_skin_mechanical_matrix(entry: dict[str, Any], game_root: Path, actor_path: Path | None, visual_rel: str, writer: GameFileWriter) -> dict[str, Any]:
    if not visual_rel:
        return {"changed": False, "skipped": "visual preset missing", "mechanicalPresets": []}
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    mechanical_rels = _target_mechanical_preset_refs_for_entry(entry, game_root, actor_path)
    known_mechanical_rels = _known_mechanical_preset_refs_for_entry(entry, game_root, actor_path)
    old_matrix_files = _file_rel_values(files, {"carfinalstates", "thumbnails", "thumbnail_mips_list"})
    changed = False
    mechanical_results: list[dict[str, Any]] = []
    finalstate_results: list[dict[str, Any]] = []
    thumbnail_results: list[dict[str, Any]] = []
    cleanup_results: list[dict[str, Any]] = []
    template_mechanical_by_preset: dict[str, str] = {}
    for mechanical_rel in mechanical_rels:
        mechanical_path = _disk_from_game_rel(game_root, mechanical_rel)
        if mechanical_path and mechanical_path.exists():
            did_change, _backup = patch_mechanicalpreset_visuals_file(mechanical_path, visual_rel, writer=writer)
            changed = changed or did_change
            mechanical_results.append({"mechanical": mechanical_rel, "status": "updated" if did_change else "current"})
        finalstate = _write_skin_carfinalstate_for_mechanical(
            entry,
            game_root,
            mechanical_rel,
            visual_rel,
            writer,
            template_mechanical_rel=str(template_mechanical_by_preset.get(mechanical_rel) or ""),
        )
        changed = changed or finalstate.get("status") == "updated"
        finalstate_results.append(finalstate)
        thumbnail = _write_skin_thumbnail_for_mechanical(entry, game_root, mechanical_rel, visual_rel, writer)
        changed = changed or thumbnail.get("status") == "updated"
        thumbnail_results.append(thumbnail)
    carfinalstates = [str(row.get("rel") or "") for row in finalstate_results if row.get("rel")]
    thumbnails = [str(row.get("rel") or "") for row in thumbnail_results if row.get("rel")]
    thumbnail_mips = [str(row.get("mipsRel") or "") for row in thumbnail_results if row.get("mipsRel")]
    target_mechanical_keys = {row.lower() for row in mechanical_rels}
    for mechanical_rel in known_mechanical_rels:
        if mechanical_rel.lower() in target_mechanical_keys:
            continue
        mechanical_path = _disk_from_game_rel(game_root, mechanical_rel)
        if mechanical_path and mechanical_path.exists():
            did_cleanup = _remove_mechanical_visual_refs(mechanical_path, [visual_rel], writer, "mechanical incompatible visual cleanup")
            changed = changed or did_cleanup
            cleanup_results.append({"mechanical": mechanical_rel, "status": "removed" if did_cleanup else "current"})
    current_matrix_files = {normalize_skin_asset_rel_path(row) for row in [*carfinalstates, *thumbnails, *thumbnail_mips] if row}
    obsolete_files = 0
    for kind, rel_norm in old_matrix_files:
        if not rel_norm or normalize_skin_asset_rel_path(rel_norm) in current_matrix_files:
            continue
        target = game_root / Path(*split_content_path(rel_norm))
        if target.exists() and target.is_file():
            writer.delete_file(target, label="obsolete mechanical matrix file", backup=kind not in {"thumbnail", "thumbnail_mips", "thumbnails", "thumbnail_mips_list"})
            changed = True
            obsolete_files += 1
    if mechanical_rels:
        files["mechanical_presets"] = mechanical_rels
        files["carfinalstates"] = carfinalstates
        files["thumbnails"] = thumbnails
        files["thumbnail_mips_list"] = thumbnail_mips
        files["mechanical_preset"] = mechanical_rels[0]
        if carfinalstates:
            files["carfinalstate"] = carfinalstates[0]
        if thumbnails:
            files["thumbnail"] = thumbnails[0]
        if thumbnail_mips:
            files["thumbnail_mips"] = thumbnail_mips[0]
        entry["files"] = files
    return {
        "changed": changed,
        "mechanicalPresets": mechanical_rels,
        "variantNotice": _mechanical_variant_notice(game_root, known_mechanical_rels, mechanical_rels),
        "mechanicalResults": mechanical_results,
        "cleanupResults": cleanup_results,
        "carfinalstates": finalstate_results,
        "thumbnails": thumbnail_results,
        "carfinalstateCount": len(carfinalstates),
        "thumbnailCount": len(thumbnails),
        "obsoleteFileCount": obsolete_files,
    }


def sync_livery_project_thumbnail_to_game_preview(entry: dict[str, Any], game_root: Path, writer: GameFileWriter) -> dict[str, Any]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    local_png = _thumbnail_png_source_for_game_sync(entry)
    local_sync: dict[str, Any] = {}
    if not (local_png and local_png.exists()):
        local_sync = sync_livery_project_thumbnail_to_local_skin(entry)
        files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
        local_png = _thumbnail_png_source_for_game_sync(entry)
        if not (local_png and local_png.exists()):
            return {"changed": False, "skipped": "local thumbnail png missing", "localSync": local_sync}

    target_texture, target_mips, reason = _game_thumbnail_target_for_entry(entry, game_root)
    if target_texture is None or target_mips is None:
        return {"changed": False, "skipped": reason, "localSync": local_sync}

    sync_state = entry.get("game_sync") if isinstance(entry.get("game_sync"), dict) else {}
    force_preview_update = bool(sync_state.get("needs_update")) and str(sync_state.get("workflow") or "") == "preview_thumbnail"
    if (
        not force_preview_update
        and
        target_texture.exists()
        and target_mips.exists()
        and local_png.stat().st_mtime <= target_mips.stat().st_mtime + 0.001
    ):
        files["thumbnail"] = game_rel_from_disk(game_root, target_texture)
        files["thumbnail_mips"] = game_rel_from_disk(game_root, target_mips)
        entry["files"] = files
        return {
            "changed": False,
            "status": "current",
            "thumbnail": str(target_texture),
            "thumbnailMips": str(target_mips),
            "localSync": local_sync,
        }

    template = find_any_generated_thumbnail_template(game_root, str(entry.get("car_content_path") or ""))
    if template is None:
        return {"changed": False, "skipped": "usable thumbnail template missing", "target": str(target_texture), "localSync": local_sync}

    before_texture = target_texture.read_bytes() if target_texture.exists() else None
    before_mips = target_mips.read_bytes() if target_mips.exists() else None
    try:
        texture_path, mips_path = write_generated_thumbnail_from_preview(
            template,
            target_texture,
            local_png,
            overwrite=True,
            writer=writer,
        )
    except RuntimeError as exc:
        if is_optional_thumbnail_generation_error(exc):
            return {
                "changed": False,
                "skipped": f"thumbnail conversion unsupported: {exc}",
                "target": str(target_texture),
                "template": str(template),
                "localSync": local_sync,
            }
        raise
    changed = True
    try:
        changed = before_texture != texture_path.read_bytes() or before_mips != mips_path.read_bytes()
    except Exception:
        changed = True
    files["thumbnail"] = game_rel_from_disk(game_root, texture_path)
    files["thumbnail_mips"] = game_rel_from_disk(game_root, mips_path)
    entry["files"] = files
    return {
        "changed": changed,
        "thumbnail": str(texture_path),
        "thumbnailMips": str(mips_path),
        "template": str(template),
        "localSync": local_sync,
    }


def sync_livery_project_thumbnail_to_game_previews(entry: dict[str, Any], game_root: Path, writer: GameFileWriter) -> dict[str, Any]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    visual_rel = normalize_game_rel_path(str(files.get("visual_preset") or ""))
    if not visual_rel:
        return {"changed": False, "skipped": "visual preset missing", "thumbnails": []}

    local_png = _local_skin_asset_path(entry, str(files.get("thumbnail_png") or ""))
    local_sync: dict[str, Any] = {}
    if not (local_png and local_png.exists()):
        local_sync = sync_livery_project_thumbnail_to_local_skin(entry)
        files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
        local_png = _local_skin_asset_path(entry, str(files.get("thumbnail_png") or ""))
        if not (local_png and local_png.exists()):
            return {"changed": False, "skipped": "local thumbnail png missing", "localSync": local_sync, "thumbnails": []}

    actor_rel = normalize_game_rel_path(str(files.get("actor") or ""))
    actor_path = _disk_from_game_rel(game_root, actor_rel)
    mechanical_rels = _target_mechanical_preset_refs_for_entry(entry, game_root, actor_path)
    if not mechanical_rels:
        mechanical_rel = normalize_game_rel_path(str(files.get("mechanical_preset") or ""))
        if mechanical_rel:
            mechanical_rels = [mechanical_rel]
    if not mechanical_rels:
        return {"changed": False, "skipped": "mechanical preset missing", "localSync": local_sync, "thumbnails": []}

    changed = False
    thumbnail_results: list[dict[str, Any]] = []
    for mechanical_rel in mechanical_rels:
        result = _write_skin_thumbnail_for_mechanical(entry, game_root, mechanical_rel, visual_rel, writer)
        changed = changed or result.get("status") == "updated"
        thumbnail_results.append(result)

    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    thumbnails = [str(row.get("rel") or "") for row in thumbnail_results if row.get("rel")]
    thumbnail_mips = [str(row.get("mipsRel") or "") for row in thumbnail_results if row.get("mipsRel")]
    if thumbnails:
        files["thumbnails"] = thumbnails
        files["thumbnail"] = thumbnails[0]
    if thumbnail_mips:
        files["thumbnail_mips_list"] = thumbnail_mips
        files["thumbnail_mips"] = thumbnail_mips[0]
    entry["files"] = files
    return {
        "changed": changed,
        "status": "updated" if changed else ("current" if thumbnails else "skipped"),
        "mechanicalPresets": mechanical_rels,
        "thumbnailCount": len(thumbnails),
        "thumbnails": thumbnail_results,
        "localSync": local_sync,
    }


def _resolve_my_skin_game_root(
    entry: dict[str, Any],
    preferred_root: str | Path | None = None,
) -> Path:
    raw_candidates = [
        str(entry.get("game_root") or "").strip(),
        str(preferred_root or "").strip(),
        str(get_default_root_folder() or "").strip(),
    ]
    storage_root = get_portable_storage_root().resolve()
    candidates: list[Path] = []
    seen: set[str] = set()
    for raw in raw_candidates:
        if not raw:
            continue
        candidate = normalize_game_root(raw)
        key = str(candidate).lower()
        if key in seen:
            continue
        seen.add(key)
        if _path_is_within(candidate, storage_root):
            continue
        candidates.append(candidate)
    for candidate in candidates:
        if (candidate / "content" / "cars").is_dir():
            return candidate
    if candidates:
        return candidates[0]
    raise FileNotFoundError(
        "No valid AC EVO game root is available for this livery. "
        "Configure the AC EVO Game Root in LiveryLab before using a game-file action."
    )


def set_my_skin_active(
    skin_key: str,
    active: bool = True,
    writer: GameFileWriter | None = None,
    *,
    delete_savedcars_on_deactivate: bool = True,
) -> dict[str, Any]:
    entry, skins, index = _get_my_skin_entry(skin_key)
    policy = skin_policy.policy_for_skin(entry)
    editable_skin = bool(policy.get("editable"))
    locked_derived_skin = official_to_advanced_import.uses_locked_project_workspace(entry)
    project_type = project_types.project_type_for_entry(entry)
    official_project = project_type == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY
    game_root = _resolve_my_skin_game_root(entry)
    entry["game_root"] = str(game_root)
    runtime_guard: dict[str, Any] = {}
    if not official_project:
        runtime_guard = runtime_write_guards.assert_advanced_game_writes_ready(
            game_root,
            package_manifest_root=get_package_state_manifest_root(),
            action="activate, update or deactivate an Advanced LiveryLab skin in game",
        )
    if active and _road_plate_custom_source_missing(entry):
        plate_path = _road_plate_effective_png_path_for_entry(entry)
        raise RuntimeError(
            "Custom rear plate image missing. "
            f"Restore plate_r.png in the LiveryProject folder ({plate_path}) "
            "or switch the rear plate setting back to the AC EVO default rear plate before using Activate or Update in Game."
        )
    tyre_selection_prepare: dict[str, Any] = {}
    if active:
        tyre_selection_prepare = _ensure_persisted_tyre_brand_selection(entry, game_root)
        project_asset_check = inspect_my_skin_project_assets(entry, policy)
        if project_asset_check.get("criticalCount"):
            missing = ", ".join(
                str(row.get("filename") or row.get("label") or row.get("path") or "project asset")
                for row in project_asset_check.get("critical", [])[:6]
            )
            raise RuntimeError(
                "Cannot activate this skin because required LiveryProject source files are missing: "
                f"{missing}. Restore the missing files in the LiveryProject folder before using Activate or Update in Game."
            )
        if project_type == project_types.PROJECT_TYPE_ADVANCED_EXTRACTED_CONTENT:
            banner_dependency_repair = repair_advanced_ext_banner_runtime_dependencies(entry, game_root)
            if banner_dependency_repair.get("required") and not banner_dependency_repair.get("ready"):
                raise RuntimeError(
                    "Cannot activate this Advanced skin because its local EXT_BANNER runtime files could not be prepared: "
                    f"{banner_dependency_repair.get('reason') or 'unknown banner repair error'}"
                )
        repair_missing_skin_local_material_dependencies(entry, game_root)
        dependency_check = inspect_my_skin_activation_dependencies(entry, game_root)
        if dependency_check.get("criticalCount"):
            details = "; ".join(str(row.get("message") or row.get("reason") or row) for row in dependency_check.get("critical", [])[:4])
            raise RuntimeError(f"Cannot activate this skin because required package dependencies are missing: {details}")
        ensure_multiplayer_safety_ready_for_skin_write(game_root, "activate or update My Skin in game")
    refs_changed = _normalize_my_skin_file_refs(entry)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    actor_rel = str(files.get("actor") or "").strip()
    visual_rel = str(files.get("visual_preset") or "").strip()
    cardesign_rel = str(files.get("cardesign") or "").strip()
    design_rel = str(files.get("design") or "").strip()
    mechanical_rel = str(files.get("mechanical_preset") or "").strip()
    cardesign_refs = _my_skin_ref_candidates(entry, game_root, cardesign_rel)
    visual_refs = _my_skin_ref_candidates(entry, game_root, visual_rel)
    cardesign_rel = cardesign_refs[0] if cardesign_refs else cardesign_rel
    visual_rel = visual_refs[0] if visual_refs else visual_rel
    actor_path = _disk_from_game_rel(game_root, actor_rel)
    visual_path = _disk_from_game_rel(game_root, visual_rel)
    mechanical_path = _disk_from_game_rel(game_root, mechanical_rel)
    mechanical_rels = _target_mechanical_preset_refs_for_entry(entry, game_root, actor_path)
    mechanical_paths = [path for path in (_disk_from_game_rel(game_root, rel) for rel in mechanical_rels) if path]
    deactivation_cleanup: dict[str, Any] = {}
    if not active and visual_refs:
        deactivation_cleanup = _my_skin_deactivation_cleanup_plan(entry, game_root, actor_path, visual_refs or [visual_rel])
        cleanup_mechanical_rels = deactivation_cleanup.get("mechanical_rels")
        cleanup_mechanical_paths = deactivation_cleanup.get("mechanical_paths")
        if isinstance(cleanup_mechanical_rels, list) and cleanup_mechanical_rels:
            mechanical_rels = [str(row) for row in cleanup_mechanical_rels]
        if isinstance(cleanup_mechanical_paths, list) and cleanup_mechanical_paths:
            mechanical_paths = [Path(str(path)) for path in cleanup_mechanical_paths]
    savedcars_delete: dict[str, Any] = {}
    if not active and delete_savedcars_on_deactivate:
        savedcars_delete = delete_my_skin_savedcars(entry, game_root)
    local_root = _entry_local_root(entry)
    thumbnail_local_update: dict[str, Any] = {}
    if active and local_root and editable_skin:
        thumbnail_local_update = sync_livery_project_thumbnail_to_local_skin(entry)
        files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    sync_targets: list[Path] = []
    if active and local_root:
        sync_kinds = {
            "cardesign",
            "design",
            "skin_folder",
            "material",
            "visual_preset",
            "carfinalstate",
            "carfinalstates",
            "thumbnail",
            "thumbnail_mips",
            "thumbnail_mips_list",
            "thumbnails",
            "thumbnail_png",
        }
        for _kind, rel_norm in _file_rel_values(files, sync_kinds):
            source = local_root / Path(*split_content_path(rel_norm)) if rel_norm else None
            if source and source.exists():
                sync_targets.append(game_root / Path(*split_content_path(rel_norm)))
        thumbnail_target, thumbnail_mips_target, _thumbnail_target_reason = _game_thumbnail_target_for_entry(entry, game_root)
        if thumbnail_target is not None:
            sync_targets.append(thumbnail_target)
        if thumbnail_mips_target is not None:
            sync_targets.append(thumbnail_mips_target)
        if NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED:
            bake = entry.get("numberplate_bake") if isinstance(entry.get("numberplate_bake"), dict) else {}
            surfaces = bake.get("surfaces") if isinstance(bake.get("surfaces"), dict) else {}
            for surface in surfaces.values():
                if not isinstance(surface, dict):
                    continue
                for rel_key in ("textureRel", "mipsRel"):
                    rel_norm = normalize_skin_asset_rel_path(str(surface.get(rel_key) or ""))
                    source = local_root / Path(*split_content_path(rel_norm)) if rel_norm else None
                    if source and source.exists():
                        sync_targets.append(game_root / Path(*split_content_path(rel_norm)))
        if _driver_profile_selection(entry):
            skin_folder_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
            for rel_norm in (skin_folder_rel, visual_rel, design_rel):
                if rel_norm:
                    sync_targets.append(game_root / Path(*split_content_path(rel_norm)))
            if skin_folder_rel:
                sync_targets.append(game_root / Path(*split_content_path(skin_folder_rel)) / "driver")
    loc_candidates: list[Path] = []
    if active and visual_rel and str(entry.get("visual_loc_key") or "").strip():
        loc_candidates = iter_car_loc_files(game_root)
    own_writer = writer is None
    if writer is None:
        writer = GameFileWriter(
            game_root,
            "toggle My Skin active state",
            [
                path
                for path in (
                    *sync_targets,
                    actor_path,
                    visual_path,
                    mechanical_path,
                    *mechanical_paths,
                    *(deactivation_cleanup.get("matrix_paths") or []),
                    game_root / "system" / "savedcars",
                    game_root / "content" / "cars" / split_content_path(str(entry.get("car_content_path") or "content\\cars\\car"))[-1] / "generated" / "thumbnails",
                    *loc_candidates,
                )
                if path
            ],
            backups_enabled=False,
        )

    actions: list[str] = []
    changed = False
    local_texture_update: dict[str, Any] = {}
    thumbnail_game_update: dict[str, Any] = {}
    driver_profile_sync: dict[str, Any] = {}
    if tyre_selection_prepare.get("enabled"):
        actions.extend(str(row) for row in tyre_selection_prepare.get("actions") or [] if str(row).strip())
        changed = changed or bool(tyre_selection_prepare.get("changed"))

    if not active and savedcars_delete:
        deleted_count = len(savedcars_delete.get("deleted") or [])
        matched_count = len(savedcars_delete.get("matches") or [])
        if deleted_count:
            changed = True
            actions.append(f"user garage SavedCars removed ({deleted_count})")
        elif matched_count:
            actions.append(f"user garage SavedCars matched but not removed ({matched_count})")
        if savedcars_delete.get("errors"):
            actions.append(f"user garage SavedCars warning: {savedcars_delete.get('errors')[0]}")

    if active:
        if local_root:
            if editable_skin:
                sf25_neutral_sync = _ensure_sf25_neutral_sponsor_materials(entry, game_root)
                if sf25_neutral_sync.get("changed"):
                    changed = True
                    patched_count = len(sf25_neutral_sync.get("patched") or [])
                    actions.append(f"Ferrari SF25 sponsor materials neutralized ({patched_count} files)")
                f2004_neutral_sync = _ensure_f2004_neutral_overlay_materials(entry, game_root)
                if f2004_neutral_sync.get("changed"):
                    changed = True
                    patched_count = len(f2004_neutral_sync.get("patched") or [])
                    actions.append(f"Ferrari F2004 overlay materials neutralized ({patched_count} files)")
                weissach_sticker_sync = _ensure_gt3rs_weissach_stickers_removed(entry)
                if weissach_sticker_sync.get("changed"):
                    changed = True
                    actions.append("GT3 RS Weissach sticker overlays removed from local skin")
                if thumbnail_local_update.get("changed"):
                    changed = True
                    actions.append("LiveryProjects thumbnail synced to local skin")
                elif thumbnail_local_update.get("project"):
                    actions.append("LiveryProjects thumbnail already current")
                weissach_carbon_sync = _ensure_gt3rs_weissach_carbon_ext_skin_neutralized(entry)
                if weissach_carbon_sync.get("changed"):
                    changed = True
                    patched_count = len(weissach_carbon_sync.get("patched") or [])
                    actions.append(f"GT3 RS Weissach carbon EXT_SKIN materials neutralized ({patched_count} files)")
                cayman_weissach_carbon_sync = _ensure_cayman_gt4rs_weissach_carbon_ext_skin_neutralized(entry)
                if cayman_weissach_carbon_sync.get("changed"):
                    changed = True
                    patched_count = len(cayman_weissach_carbon_sync.get("patched") or [])
                    actions.append(f"718 Cayman GT4 RS Weissach carbon EXT_SKIN materials neutralized ({patched_count} files)")
            if editable_skin or locked_derived_skin:
                local_texture_update = _sync_livery_project_textures_to_local_skin(entry, game_root)
                updated_textures = int(local_texture_update.get("updated") or 0)
                if updated_textures:
                    changed = True
                    actions.append(f"LiveryProjects textures applied to local skin ({updated_textures} files)")
            else:
                actions.append("read-only package project texture sync skipped")
            actions.append(f"local source: {local_root}")
            synced = _sync_local_my_skin_to_game(entry, game_root, writer)
            if not synced and not any(
                (game_root / Path(*split_content_path(rel_norm))).exists()
                for _kind, rel_norm in _file_rel_values(files, {"cardesign", "design", "skin_folder", "material", "visual_preset", "carfinalstate", "carfinalstates", "thumbnail", "thumbnails", "thumbnail_mips", "thumbnail_mips_list", "thumbnail_png"})
            ):
                raise FileNotFoundError(f"No local livery files found to sync. Local source: {local_root}")
            actions.extend(synced or ["local files already synced"])
            changed = changed or bool(synced)
            sync_count = sum(1 for row in writer.results if str(row.label).startswith("activate local "))
            actions.append(f"game sync writes: {sync_count}")
            baked_synced = _sync_local_numberplate_bake_to_game(entry, game_root, writer) if NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED else []
            if baked_synced:
                changed = True
                actions.extend(baked_synced)
            tyre_assets_synced = _sync_custom_tyre_brand_assets_for_entry(entry, game_root, writer)
            if tyre_assets_synced:
                changed = True
                actions.extend(tyre_assets_synced)
            driver_profile_sync = _sync_driver_profile_assets_for_entry(entry, game_root, writer)
            if driver_profile_sync.get("changed"):
                changed = True
                actions.append(
                    "driver profile synced "
                    f"({driver_profile_sync.get('copiedCount') or 0} copied, "
                    f"{driver_profile_sync.get('patchedCount') or 0} patched)"
                )
            elif driver_profile_sync.get("skipped"):
                actions.append(f"driver profile skipped: {driver_profile_sync.get('skipped')}")
            else:
                actions.append("driver profile already current")
            project_sync = driver_profile_sync.get("projectSync") if isinstance(driver_profile_sync.get("projectSync"), dict) else {}
            if project_sync.get("changed"):
                actions.append(f"driver project synced to profile ({project_sync.get('copiedCount') or 0} files)")
            elif project_sync.get("skipped") and project_sync.get("skipped") != "driver project missing":
                actions.append(f"driver project skipped: {project_sync.get('skipped')}")
            for warning in driver_profile_sync.get("warnings") or []:
                actions.append(f"driver profile warning: {warning}")
            thumbnail_game_update = sync_livery_project_thumbnail_to_game_preview(entry, game_root, writer)
            if thumbnail_game_update.get("changed"):
                changed = True
                actions.append("EVO livery preview thumbnail updated")
            elif thumbnail_game_update.get("status") == "current":
                actions.append("EVO livery preview thumbnail already current")
            elif thumbnail_game_update.get("skipped"):
                actions.append(f"EVO livery preview thumbnail skipped: {thumbnail_game_update.get('skipped')}")
        numberplate_variant = entry.get("numberplate_variant") if isinstance(entry.get("numberplate_variant"), dict) else {}
        numberplate_source = str(numberplate_variant.get("source_type") or numberplate_variant.get("numberplate_source") or "").strip().lower()
        visual_start_number = _sanitize_start_number(
            numberplate_variant.get("start_number") or entry.get("start_number") or ""
        )
        if numberplate_source in NUMBERPLATE_NONE_SOURCE_TYPES and visual_start_number and visual_path and visual_path.exists():
            did_change, _backup = update_visualpreset_file(visual_path, start_number=visual_start_number, writer=writer)
            changed = changed or did_change
            actions.append(f"visual start number {'synced' if did_change else 'already current'} ({visual_start_number})")
        publishes_owned_numberplate_variant = numberplate_variant.get("owns_actor_numberplate") is True
        numberplate_publish = (
            publish_local_numberplate_variant_to_game_actor(entry, game_root, writer)
            if publishes_owned_numberplate_variant
            else publish_local_numberplate_visibility_to_game_actor(entry, game_root, writer)
        )
        if numberplate_publish.get("actorChanged"):
            changed = True
            if publishes_owned_numberplate_variant:
                actions.append(
                    "numberplate actor placements synced "
                    f"({numberplate_publish.get('placementUpdatesTotal', 0)} placements, "
                    f"{numberplate_publish.get('numberplateUpdates', 0)} numberplates)"
                )
            else:
                actions.append(f"numberplate visibility synced ({numberplate_publish.get('numberplateUpdates', 0)} numberplates)")
        if numberplate_publish.get("actorResourcesRepaired"):
            changed = True
            actions.append(f"numberplate actor stale resources repaired ({len(numberplate_publish.get('repairedResources') or [])} resources)")
        if numberplate_publish.get("selectorChanged"):
            changed = True
            actions.append("numberplate visual selector synced")
        if (
            not numberplate_publish.get("actorChanged")
            and not numberplate_publish.get("actorResourcesRepaired")
            and not numberplate_publish.get("selectorChanged")
            and numberplate_publish.get("skipped")
            and numberplate_publish.get("skipped") not in {
                "skin has no LiveryLab-owned numberplate variant",
                "skin has no numberplate visibility changes",
                "numberplate source is not AC EVO default/no numberplate",
                "AC EVO empty numberplate selector needs no actor changes",
            }
        ):
            actions.append(f"numberplate actor sync skipped: {numberplate_publish.get('skipped')}")
        actions.append(f"game target: {game_root}")
        if actor_path and actor_path.exists() and cardesign_rel:
            legacy = _legacy_ref_candidates(cardesign_refs)
            if legacy:
                did_cleanup = _remove_actor_cardesign_refs(actor_path, legacy, writer, "actor legacy cardesign cleanup")
                changed = changed or did_cleanup
                if did_cleanup:
                    actions.append("actor legacy cardesign refs removed")
            did_change, _backup = patch_actor_file(actor_path, cardesign_rel, writer=writer)
            changed = changed or did_change
            actions.append(f"actor cardesign {'added' if did_change else 'already present'}")
        if actor_path and actor_path.exists() and visual_rel:
            legacy = _legacy_ref_candidates(visual_refs)
            if legacy:
                did_cleanup = _remove_actor_visual_refs(actor_path, legacy, writer, "actor legacy visual cleanup")
                changed = changed or did_cleanup
                if did_cleanup:
                    actions.append("actor legacy visual refs removed")
            did_change, _backup = patch_actor_visualpreset_file(actor_path, visual_rel, writer=writer)
            changed = changed or did_change
            actions.append(f"actor visual {'added' if did_change else 'already present'}")
        if visual_rel:
            legacy = _legacy_ref_candidates(visual_refs)
            if legacy:
                for path in mechanical_paths:
                    if path.exists():
                        did_cleanup = _remove_mechanical_visual_refs(path, legacy, writer, "mechanical legacy visual cleanup")
                        changed = changed or did_cleanup
                if legacy:
                    actions.append("mechanical legacy visual refs checked")
            matrix_update = sync_skin_mechanical_matrix(entry, game_root, actor_path, visual_rel, writer)
            changed = changed or bool(matrix_update.get("changed"))
            variant_notice = matrix_update.get("variantNotice") if isinstance(matrix_update.get("variantNotice"), dict) else {}
            if variant_notice.get("message"):
                actions.append(str(variant_notice.get("message")))
            actions.append(
                "mechanical matrix synced "
                f"({len(matrix_update.get('mechanicalPresets') or [])} presets, "
                f"{matrix_update.get('carfinalstateCount') or 0} carfinalstates)"
            )
            ext_banner_sync = _ensure_ext_banner_for_game_entry(
                entry,
                game_root,
                visual_rel,
                [str(row) for row in (matrix_update.get("mechanicalPresets") or mechanical_rels)],
                writer,
            )
            changed = changed or bool(ext_banner_sync.get("changed"))
            if ext_banner_sync.get("skipped"):
                actions.append(f"EXT_BANNER skipped: {ext_banner_sync.get('skipped')}")
            else:
                actions.append(
                    "EXT_BANNER synced "
                    f"(design={'updated' if ext_banner_sync.get('designChanged') else 'current'}, "
                    f"visual={'cleaned' if ext_banner_sync.get('visualRemoved') else 'current'}, "
                    f"finalstates={ext_banner_sync.get('finalstateChanged') or 0} updated/"
                    f"{ext_banner_sync.get('finalstateCurrent') or 0} current, "
                    f"local={ext_banner_sync.get('localFinalstateChanged') or 0} updated/"
                    f"{ext_banner_sync.get('localFinalstateCurrent') or 0} current, "
                    f"savedcars={ext_banner_sync.get('savedcarChanged') or 0} updated/"
                    f"{ext_banner_sync.get('savedcarCurrent') or 0} current)"
                )
                for error in (ext_banner_sync.get("savedcarErrors") or [])[:3]:
                    actions.append(f"EXT_BANNER saved car warning: {error}")
            road_plate_sync = _sync_road_plate_for_entry(
                entry,
                game_root,
                visual_rel,
                writer,
                matrix_update.get("mechanicalPresets") or mechanical_rels,
            )
            changed = changed or bool(road_plate_sync.get("changed"))
            if road_plate_sync.get("skipped"):
                skipped = str(road_plate_sync.get("skipped") or "")
                if skipped not in {"PLATE_R resource missing", "project plate PNG missing", "no generated road plate metadata"}:
                    actions.append(f"road plate skipped: {skipped}")
            elif road_plate_sync.get("status") in {"removed", "current"} and road_plate_sync.get("compatiblePartRel"):
                mechanical_changed = sum(1 for row in (road_plate_sync.get("mechanicals") or []) if isinstance(row, dict) and row.get("changed"))
                finalstate_changed = sum(1 for row in (road_plate_sync.get("finalstates") or []) if isinstance(row, dict) and row.get("changed"))
                savedcar_changed = sum(1 for row in (road_plate_sync.get("savedcars") or []) if isinstance(row, dict) and row.get("changed"))
                actions.append(
                    f"custom rear plate {'deactivated' if road_plate_sync.get('status') == 'removed' else 'already inactive'} "
                    f"(files={len(road_plate_sync.get('deleted') or [])}, "
                    f"mechanicals={mechanical_changed}, "
                    f"finalstates={finalstate_changed}, savedcars={savedcar_changed})"
                )
            else:
                mechanical_changed = sum(1 for row in (road_plate_sync.get("mechanicals") or []) if isinstance(row, dict) and row.get("changed"))
                finalstate_changed = sum(1 for row in (road_plate_sync.get("finalstates") or []) if isinstance(row, dict) and row.get("changed"))
                savedcar_changed = sum(1 for row in (road_plate_sync.get("savedcars") or []) if isinstance(row, dict) and row.get("changed"))
                actions.append(
                    "road plate synced "
                    f"(assets={road_plate_sync.get('writeCount') or 0}, "
                    f"mechanicals={mechanical_changed}, "
                    f"finalstates={finalstate_changed}, savedcars={savedcar_changed})"
                )
                for error in (road_plate_sync.get("savedcarErrors") or [])[:3]:
                    actions.append(f"road plate saved car warning: {error}")
        visual_loc_key = str(entry.get("visual_loc_key") or "").strip()
        if visual_loc_key:
            seen_loc: set[str] = set()
            for loc_path in loc_candidates:
                key = str(loc_path).lower()
                if key in seen_loc:
                    continue
                seen_loc.add(key)
                did_change, _backup = upsert_loc_entry(loc_path, visual_loc_key, str(entry.get("display_name") or ""), writer=writer)
                changed = changed or did_change
            if loc_candidates:
                entry["loc_files"] = [value for value in [game_rel_from_disk(game_root, path) for path in loc_candidates] if value]
                actions.append("localization entries synced")
    else:
        if visual_rel:
            removed_any = False
            for path in mechanical_paths:
                if path.exists():
                    did_change = _remove_mechanical_visual_refs(path, visual_refs or [visual_rel], writer, "mechanical visual removal")
                    changed = changed or did_change
                    removed_any = removed_any or did_change
            files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
            matrix_paths = deactivation_cleanup.get("matrix_paths") if isinstance(deactivation_cleanup, dict) else []
            if not matrix_paths:
                matrix_paths = [
                    game_root / Path(*split_content_path(rel_norm))
                    for _kind, rel_norm in _file_rel_values(
                        files,
                        {"carfinalstate", "carfinalstates", "thumbnail", "thumbnail_mips", "thumbnails", "thumbnail_mips_list"},
                    )
                ]
            deleted_matrix = 0
            seen_matrix: set[str] = set()
            for target in matrix_paths:
                target = Path(str(target))
                key = str(target.resolve()).lower()
                if key in seen_matrix:
                    continue
                seen_matrix.add(key)
                if target.exists() and target.is_file():
                    writer.delete_file(target, label="mechanical matrix removal", backup=not _is_generated_thumbnail_ref(target))
                    changed = True
                    deleted_matrix += 1
            actions.append(
                f"mechanical matrix {'removed' if removed_any or deleted_matrix else 'already absent'}"
                f" ({len(mechanical_paths)} presets checked, {deleted_matrix} files removed)"
            )
        if actor_path and actor_path.exists() and visual_rel:
            did_change = _remove_actor_visual_refs(actor_path, visual_refs or [visual_rel], writer, "actor visual removal")
            changed = changed or did_change
            actions.append(f"actor visual {'removed' if did_change else 'already absent'}")
        if actor_path and actor_path.exists() and cardesign_rel:
            did_change = _remove_actor_cardesign_refs(actor_path, cardesign_refs or [cardesign_rel], writer, "actor cardesign removal")
            changed = changed or did_change
            actions.append(f"actor cardesign {'removed' if did_change else 'already absent'}")

    entry["active"] = bool(active)
    _clear_my_skin_game_sync(entry)
    skins[index] = entry
    saved = save_my_skins(skins)
    safety_refresh = refresh_multiplayer_safety_after_my_skin_entry_change(
        entry,
        game_root,
        "set_my_skin_active",
        bypass_liverylab_safety=False,
    ) if active else {"attempted": False, "status": "skipped", "reason": "skin deactivated", "workflow": "set_my_skin_active"}
    return {
        "item": entry,
        "changed": changed,
        "actions": actions,
        "settingsPath": saved,
        "safetyRefresh": safety_refresh,
        "writer": writer.to_dict() if own_writer and writer else {},
        "localTextureUpdate": local_texture_update,
        "thumbnailLocalUpdate": thumbnail_local_update,
        "thumbnailGameUpdate": thumbnail_game_update,
        "driverProfileSync": driver_profile_sync,
        "savedCarsDelete": savedcars_delete,
        "runtimeGuard": runtime_guard,
    }


def get_active_advanced_livery_refresh_status(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder())
    result = advanced_content_setup.active_livery_refresh_status(
        get_package_state_manifest_root(),
        game_root,
    )
    result["root"] = str(game_root)
    return _json_ready(result)


def refresh_active_advanced_liveries(
    root: str | Path | None = None,
    progress: Callable[[dict[str, Any]], None] | None = None,
) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder())
    runtime_switch.require_game_closed()
    package = package_state.content_package_status(game_root, get_package_state_manifest_root())
    setup = advanced_content_setup.status(get_package_state_manifest_root(), game_root, package)
    if setup.get("ready") is not True or setup.get("legacy") is True:
        raise RuntimeError(
            str(setup.get("reason") or "A current Advanced full extraction is required before active liveries can be refreshed.")
        )
    runtime_write_guards.assert_advanced_game_writes_ready(
        game_root,
        package_manifest_root=get_package_state_manifest_root(),
        action="refresh active Advanced LiveryLab skins after a full extraction",
    )

    refresh = advanced_content_setup.active_livery_refresh_status(get_package_state_manifest_root(), game_root)
    extraction_id = str(refresh.get("extractionId") or "").strip()
    if not extraction_id:
        raise RuntimeError("The completed Advanced extraction has no refresh identifier.")
    if str(refresh.get("state") or "") == "completed":
        return _json_ready({**refresh, "root": game_root, "skipped": True, "reason": "already refreshed for this extraction"})

    items = dict(refresh.get("items") or {}) if isinstance(refresh.get("items"), dict) else {}
    if not items:
        candidates: list[dict[str, Any]] = []
        for entry in load_my_skins():
            if not _is_liverylab_managed_my_skin(entry):
                continue
            if project_types.project_type_for_entry(entry) == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
                continue
            if entry.get("active", True) is False:
                continue
            key = str(entry.get("key") or "").strip()
            entry_root_text = str(entry.get("game_root") or "").strip()
            if not key or not entry_root_text or normalize_game_root(entry_root_text) != game_root:
                continue
            candidates.append(entry)
        candidates.sort(key=lambda item: str(item.get("key") or "").lower())
        items = {
            str(entry.get("key")): {
                "status": "pending",
                "displayName": str(entry.get("display_name") or entry.get("skin_id") or entry.get("key") or "Advanced livery"),
                "updatedAt": "",
                "error": "",
            }
            for entry in candidates
        }
        refresh["items"] = items
        refresh["state"] = "pending"
        refresh["summary"] = {"total": len(items), "refreshed": 0, "failed": 0, "pending": len(items)}
        refresh = advanced_content_setup.save_active_livery_refresh(
            get_package_state_manifest_root(),
            game_root,
            refresh,
            expected_extraction_id=extraction_id,
        )

    def save_progress() -> dict[str, Any]:
        rows = list(items.values())
        summary = {
            "total": len(rows),
            "refreshed": sum(1 for row in rows if str(row.get("status") or "") == "refreshed"),
            "failed": sum(1 for row in rows if str(row.get("status") or "") == "failed"),
            "pending": sum(1 for row in rows if str(row.get("status") or "") == "pending"),
        }
        refresh["items"] = items
        refresh["summary"] = summary
        refresh["state"] = "completed" if summary["failed"] == 0 and summary["pending"] == 0 else "partial"
        return advanced_content_setup.save_active_livery_refresh(
            get_package_state_manifest_root(),
            game_root,
            refresh,
            expected_extraction_id=extraction_id,
        )

    pending_keys = [key for key, row in items.items() if str(row.get("status") or "") != "refreshed"]
    for index, key in enumerate(pending_keys, start=1):
        row = dict(items.get(key) or {})
        if progress:
            try:
                progress({
                    "phase": "refresh_active_advanced_liveries",
                    "index": index,
                    "count": len(pending_keys),
                    "skinKey": key,
                    "displayName": row.get("displayName") or key,
                })
            except Exception:
                pass
        try:
            result = set_my_skin_active(key, True)
        except Exception as exc:
            row.update({
                "status": "failed",
                "updatedAt": datetime.now().isoformat(timespec="seconds"),
                "error": str(exc).strip() or exc.__class__.__name__,
            })
        else:
            item = result.get("item") if isinstance(result.get("item"), dict) else {}
            row.update({
                "status": "refreshed",
                "displayName": str(item.get("display_name") or row.get("displayName") or key),
                "updatedAt": datetime.now().isoformat(timespec="seconds"),
                "error": "",
            })
        items[key] = row
        refresh = save_progress()

    if not pending_keys:
        refresh = save_progress()
    failures = [
        {"skinKey": key, "displayName": row.get("displayName") or key, "error": row.get("error") or "Refresh failed."}
        for key, row in items.items()
        if str(row.get("status") or "") == "failed"
    ]
    return _json_ready({**refresh, "root": game_root, "failures": failures, "skipped": False})


def remove_my_skin_from_game(skin_key: str) -> dict[str, Any]:
    entry, skins, index = _get_my_skin_entry(skin_key)
    game_root = normalize_game_root(str(entry.get("game_root", "")))
    if not game_root.exists():
        raise FileNotFoundError(f"Game root not found: {game_root}")
    _normalize_my_skin_file_refs(entry)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}

    loc_candidates: list[Path] = []
    visual_loc_key = str(entry.get("visual_loc_key") or "").strip()
    if visual_loc_key:
        raw_loc_files = entry.get("loc_files", [])
        if isinstance(raw_loc_files, list):
            for rel in raw_loc_files:
                disk = _disk_from_game_rel(game_root, str(rel or ""))
                if disk and disk.exists():
                    loc_candidates.append(disk)
        if not loc_candidates:
            loc_candidates = iter_car_loc_files(game_root)

    move_candidates: list[Path] = []
    movable_kinds = {"cardesign", "design", "skin_folder", "material", "visual_preset", "thumbnail_png"}
    for _kind, rel_norm in _file_rel_values(files, movable_kinds):
        disk = _disk_from_game_rel(game_root, rel_norm)
        if disk and disk.exists():
            move_candidates.append(disk.resolve())
    move_candidates = _remove_child_paths(move_candidates)

    actor_path = _disk_from_game_rel(game_root, str(files.get("actor") or ""))
    mechanical_path = _disk_from_game_rel(game_root, str(files.get("mechanical_preset") or ""))
    visual_refs = _my_skin_ref_candidates(entry, game_root, str(files.get("visual_preset") or ""))
    cleanup_plan = _my_skin_deactivation_cleanup_plan(entry, game_root, actor_path, visual_refs)
    writer_targets = [
        path
        for path in (
            actor_path,
            mechanical_path,
            *(cleanup_plan.get("mechanical_paths") or []),
            *(cleanup_plan.get("matrix_paths") or []),
            *loc_candidates,
            *(path for path in move_candidates if _path_is_within(path, game_root)),
        )
        if path
    ]
    writer = GameFileWriter(game_root, "remove My Skin from game", writer_targets)

    deactivation = set_my_skin_active(skin_key, False, writer=writer, delete_savedcars_on_deactivate=False)
    road_plate_cleanup: dict[str, Any] = {}
    if isinstance(entry.get("road_plate"), dict):
        try:
            road_plate_cleanup = _cleanup_road_plate_for_entry(
                dict(entry),
                game_root,
                visual_refs[0] if visual_refs else str(files.get("visual_preset") or ""),
                writer,
                cleanup_plan.get("mechanical_rels") or [],
            )
        except Exception as exc:
            road_plate_cleanup = {"changed": False, "error": str(exc)}

    loc_changes: list[dict[str, Any]] = []
    if visual_loc_key:
        seen_loc: set[str] = set()
        for loc_path in loc_candidates:
            key = str(loc_path).lower()
            if key in seen_loc:
                continue
            seen_loc.add(key)
            changed, backup = remove_loc_entry(loc_path, visual_loc_key, writer=writer)
            loc_changes.append({"path": loc_path, "changed": changed, "backup": backup})

    trash_dir = _make_trash_session_dir(game_root, entry)
    savedcars_quarantine = quarantine_my_skin_savedcars(entry, trash_dir)
    moved: list[str] = []
    missing: list[str] = []
    for path in move_candidates:
        if not path.exists():
            try:
                if _is_local_skin_storage_path(path):
                    missing.append(norm_slashes(str(_local_skin_storage_trash_relative(path))))
                else:
                    missing.append(norm_slashes(str(path.resolve().relative_to(game_root.resolve()))))
            except Exception:
                missing.append(str(path))
            continue
        if _is_local_skin_storage_path(path):
            moved.append(_move_local_skin_storage_to_trash(path, trash_dir))
        else:
            moved.append(_move_path_to_trash(path, game_root, trash_dir, writer=writer))

    refreshed_entry, refreshed_skins, refreshed_index = _get_my_skin_entry(skin_key)
    refreshed_entry["active"] = False
    refreshed_skins[refreshed_index] = refreshed_entry
    saved = save_my_skins(refreshed_skins)

    manifest = {
        "action": "remove_my_skin_from_game",
        "removedAt": datetime.now().isoformat(timespec="seconds"),
        "skin": refreshed_entry,
        "deactivation": deactivation,
        "road_plate_cleanup": road_plate_cleanup,
        "moved": moved,
        "missing": missing,
        "savedcars_quarantine": savedcars_quarantine,
        "loc_changes": [
            {
                "path": game_rel_from_disk(game_root, Path(str(row.get("path")))) or str(row.get("path")),
                "changed": bool(row.get("changed")),
            }
            for row in loc_changes
        ],
    }
    manifest_warning = ""
    try:
        writer.write_bytes(
            trash_dir / "remove_from_game_manifest.json",
            json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False).encode("utf-8"),
            label="remove from game manifest",
            backup=False,
        )
    except Exception as exc:
        manifest_warning = str(exc)

    return {
        "item": refreshed_entry,
        "trashDir": trash_dir,
        "moved": moved,
        "missing": missing,
        "savedCarsQuarantine": savedcars_quarantine,
        "locChanges": loc_changes,
        "deactivation": deactivation,
        "roadPlateCleanup": road_plate_cleanup,
        "settingsPath": saved,
        "manifestWarning": manifest_warning,
        "writer": writer.to_dict(),
    }


RESTORE_ACE_DELETE_FILE_KINDS = {
    "cardesign",
    "design",
    "skin_folder",
    "material",
    "visual_preset",
    "carfinalstate",
    "carfinalstates",
    "thumbnail",
    "thumbnail_mips",
    "thumbnail_mips_list",
    "thumbnails",
    "thumbnail_png",
}
RESTORE_ACE_SAVEDCAR_REF_KINDS = {
    "cardesign",
    "design",
    "skin_folder",
    "material",
    "visual_preset",
    "carfinalstate",
    "carfinalstates",
}


MY_SKIN_SAVEDCAR_CLEANUP_REF_KINDS = RESTORE_ACE_SAVEDCAR_REF_KINDS


def _restore_ace_session_dir() -> Path:
    root = get_user_data_root() / "BackToDefaultRestores" / datetime.now().strftime("%Y%m%d_%H%M%S")
    index = 1
    target = root
    while target.exists():
        index += 1
        target = root.with_name(f"{root.name}_{index}")
    target.mkdir(parents=True, exist_ok=True)
    return target


def _restore_ace_write_manifest(session_dir: Path, manifest: dict[str, Any]) -> Path:
    path = session_dir / "restore_ac_evo_default_manifest.json"
    path.write_text(json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False), encoding="utf-8")
    return path


def _restore_ace_path_label(game_root: Path, path: Path) -> str:
    try:
        return norm_slashes(str(path.resolve().relative_to(game_root.resolve())))
    except Exception:
        return str(path)


def _restore_ace_path_is_allowed(game_root: Path, path: Path) -> bool:
    if not _path_is_within(path, game_root):
        return False
    app_storage_names = {"liverylab", "liverylibrary", "liveryprojects", "liverypackageexport", "userdata"}
    return not any(part.lower() in app_storage_names for part in path.parts)


def _restore_ace_add_delete_path(
    rows: list[dict[str, Any]],
    seen: set[str],
    game_root: Path,
    entry: dict[str, Any],
    kind: str,
    rel: str,
) -> None:
    rel_norm = normalize_skin_asset_rel_path(rel)
    if not rel_norm:
        return
    path = game_root / Path(*split_content_path(rel_norm))
    key = str(path.resolve()).lower()
    if key in seen:
        return
    seen.add(key)
    rows.append(
        {
            "kind": kind,
            "rel": rel_norm,
            "path": path,
            "skinKey": str(entry.get("key") or ""),
            "skinId": str(entry.get("skin_id") or ""),
            "displayName": str(entry.get("display_name") or ""),
        }
    )


def _restore_ace_add_delete_disk_path(
    rows: list[dict[str, Any]],
    seen: set[str],
    game_root: Path,
    entry: dict[str, Any],
    kind: str,
    path: Path | None,
) -> None:
    if not path:
        return
    path = Path(path)
    key = str(path.resolve()).lower()
    if key in seen:
        return
    seen.add(key)
    rows.append(
        {
            "kind": kind,
            "rel": _restore_ace_path_label(game_root, path),
            "path": path,
            "skinKey": str(entry.get("key") or ""),
            "skinId": str(entry.get("skin_id") or ""),
            "displayName": str(entry.get("display_name") or ""),
        }
    )


def _restore_ace_entry_extra_owned_refs(entry: dict[str, Any]) -> list[tuple[str, str]]:
    rows: list[tuple[str, str]] = []
    bake = entry.get("numberplate_bake") if isinstance(entry.get("numberplate_bake"), dict) else {}
    surfaces = bake.get("surfaces") if isinstance(bake.get("surfaces"), dict) else {}
    for surface in surfaces.values():
        if not isinstance(surface, dict):
            continue
        for key in ("textureRel", "mipsRel"):
            rel = normalize_skin_asset_rel_path(str(surface.get(key) or ""))
            if rel:
                rows.append((f"numberplate_bake.{key}", rel))
    driver_profile = entry.get("driver_profile") if isinstance(entry.get("driver_profile"), dict) else {}
    driver_final = normalize_skin_asset_rel_path(str(driver_profile.get("appliedDriverFinalState") or ""))
    if driver_final:
        rows.append(("driver_profile.appliedDriverFinalState", driver_final))
    for key, rel in _road_plate_generated_rels_for_entry(entry).items():
        rel_norm = normalize_skin_asset_rel_path(str(rel or ""))
        if rel_norm:
            rows.append((f"road_plate.{key}", rel_norm))
    return rows


def _restore_ace_road_plate_compatible_refs(entry: dict[str, Any], game_root: Path) -> list[str]:
    rel = normalize_skin_asset_rel_path(_road_plate_generated_rels_for_entry(entry).get("compatiblePartRel") or "")
    return _my_skin_ref_candidates(entry, game_root, rel)


def _restore_ace_collect_entry_plan(entry: dict[str, Any], game_root: Path) -> dict[str, Any]:
    _normalize_my_skin_file_refs(entry)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    actor_rel = normalize_skin_asset_rel_path(str(files.get("actor") or ""))
    cardesign_rel = normalize_skin_asset_rel_path(str(files.get("cardesign") or ""))
    visual_rel = normalize_skin_asset_rel_path(str(files.get("visual_preset") or ""))
    actor_path = _disk_from_game_rel(game_root, actor_rel)
    cardesign_refs = _my_skin_ref_candidates(entry, game_root, cardesign_rel)
    visual_refs = _my_skin_ref_candidates(entry, game_root, visual_rel)
    road_plate_compatible_refs = _restore_ace_road_plate_compatible_refs(entry, game_root)

    delete_rows: list[dict[str, Any]] = []
    seen_paths: set[str] = set()
    for kind, rel in _file_rel_values(files, RESTORE_ACE_DELETE_FILE_KINDS):
        _restore_ace_add_delete_path(delete_rows, seen_paths, game_root, entry, kind, rel)
    for kind, rel in _restore_ace_entry_extra_owned_refs(entry):
        _restore_ace_add_delete_path(delete_rows, seen_paths, game_root, entry, kind, rel)

    mechanical_paths: list[Path] = []
    mechanical_rels = _mechanical_preset_refs_for_visual_refs(entry, game_root, actor_path, visual_refs)
    for rel in mechanical_rels:
        path = _disk_from_game_rel(game_root, rel)
        if path and path.exists() and path.is_file():
            mechanical_paths.append(path)
        if _is_liverylab_managed_mechanical_ref(rel):
            _restore_ace_add_delete_path(delete_rows, seen_paths, game_root, entry, "mechanical_preset", rel)
    for path in _carfinalstate_paths_for_visual_refs(game_root, visual_refs, mechanical_rels):
        _restore_ace_add_delete_disk_path(delete_rows, seen_paths, game_root, entry, "matrix_carfinalstate", path)
    for path in _generated_thumbnail_paths_for_visual_refs(entry, game_root, visual_refs, mechanical_rels):
        _restore_ace_add_delete_disk_path(delete_rows, seen_paths, game_root, entry, "matrix_thumbnail", path)

    loc_paths: list[Path] = []
    visual_loc_key = str(entry.get("visual_loc_key") or "").strip()
    if visual_loc_key:
        raw_loc_files = entry.get("loc_files", [])
        if isinstance(raw_loc_files, list):
            for rel in raw_loc_files:
                disk = _disk_from_game_rel(game_root, str(rel or ""))
                if disk and disk.exists() and disk.is_file():
                    loc_paths.append(disk)
        if not loc_paths:
            loc_paths = iter_car_loc_files(game_root)

    savedcar_refs: list[dict[str, str]] = []
    seen_refs: set[str] = set()
    for kind, rel in _file_rel_values(files, RESTORE_ACE_SAVEDCAR_REF_KINDS):
        for candidate in _my_skin_ref_candidates(entry, game_root, rel):
            key = norm_slashes(candidate).lower()
            if not key or key in seen_refs:
                continue
            seen_refs.add(key)
            savedcar_refs.append({"kind": kind, "ref": norm_slashes(candidate), "skinKey": str(entry.get("key") or "")})
    for kind, rel in _restore_ace_entry_extra_owned_refs(entry):
        for candidate in _my_skin_ref_candidates(entry, game_root, rel):
            key = norm_slashes(candidate).lower()
            if not key or key in seen_refs:
                continue
            seen_refs.add(key)
            savedcar_refs.append({"kind": kind, "ref": norm_slashes(candidate), "skinKey": str(entry.get("key") or "")})

    return {
        "entry": entry,
        "actorPath": actor_path,
        "cardesignRefs": cardesign_refs,
        "visualRefs": visual_refs,
        "roadPlateCompatibleRefs": road_plate_compatible_refs,
        "mechanicalPaths": mechanical_paths,
        "deleteRows": delete_rows,
        "locKey": visual_loc_key,
        "locPaths": loc_paths,
        "savedcarRefs": savedcar_refs,
    }


def _restore_ace_open_savedcars_dirs() -> list[Path]:
    profile_root = saved_games_paths.ace_root() / "ProfileData"
    if not profile_root.exists():
        return []
    dirs: list[Path] = []
    for profile in profile_root.iterdir():
        if not profile.is_dir():
            continue
        path = profile / "OpenData" / "SavedCars"
        if path.exists() and path.is_dir():
            dirs.append(path)
    return dirs


def _restore_ace_scan_open_savedcars(ref_rows: list[dict[str, str]]) -> tuple[list[dict[str, Any]], list[str]]:
    refs: list[dict[str, str]] = []
    seen_refs: set[str] = set()
    for row in ref_rows:
        ref = norm_slashes(str(row.get("ref") or "")).strip()
        key = ref.lower()
        if not key or key in seen_refs:
            continue
        seen_refs.add(key)
        refs.append({**row, "ref": ref})
    if not refs:
        return [], []

    hits: list[dict[str, Any]] = []
    errors: list[str] = []
    seen_hits: set[str] = set()
    try:
        savedcars_dirs = _restore_ace_open_savedcars_dirs()
    except Exception as exc:
        return [], [str(exc)]
    for savedcars_dir in savedcars_dirs:
        try:
            candidates = list(savedcars_dir.rglob("*.carfinalstate*"))
        except Exception as exc:
            errors.append(f"{savedcars_dir}: {exc}")
            continue
        for savedcar in candidates:
            try:
                if not savedcar.is_file() or _skip_savedcar_scan_path(savedcar):
                    continue
                text = savedcar.read_bytes().decode("utf-8", errors="ignore").lower()
            except Exception as exc:
                errors.append(f"{savedcar}: {exc}")
                continue
            matched: list[dict[str, str]] = []
            for row in refs:
                ref = str(row.get("ref") or "")
                variants = {ref.lower(), ref.replace("\\", "/").lower()}
                if any(variant and variant in text for variant in variants):
                    matched.append(row)
            if not matched:
                continue
            key = str(savedcar.resolve()).lower()
            if key in seen_hits:
                continue
            seen_hits.add(key)
            hits.append(
                {
                    "path": savedcar,
                    "guid": _ace_savedcar_guid_from_path(savedcar),
                    "matchedRefs": matched,
                }
            )
    return hits, errors


def _restore_ace_delete_savedcars(savedcar_hits: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[str]]:
    deleted: list[dict[str, Any]] = []
    errors: list[str] = []
    for hit in savedcar_hits:
        path = Path(str(hit.get("path") or ""))
        guid = str(hit.get("guid") or "")
        thumbnail_texture, thumbnail_mips = _ace_savedcar_thumbnail_path_for_guid(guid)
        deleted_thumbnails: list[str] = []
        try:
            if path.exists() and path.is_file():
                path.unlink()
            for thumbnail in (thumbnail_texture, thumbnail_mips):
                if thumbnail and thumbnail.exists() and thumbnail.is_file():
                    thumbnail.unlink()
                    deleted_thumbnails.append(str(thumbnail))
            deleted.append(
                {
                    "path": str(path),
                    "guid": guid,
                    "thumbnails": deleted_thumbnails,
                    "matchedRefs": hit.get("matchedRefs") or [],
                }
            )
        except Exception as exc:
            errors.append(f"{path}: {exc}")
    return deleted, errors


def _my_skin_savedcar_ref_rows(entry: dict[str, Any], game_root: Path | None = None) -> list[dict[str, str]]:
    if game_root is None:
        game_root = normalize_game_root(str(entry.get("game_root") or ""))
    _normalize_my_skin_file_refs(entry)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    rows: list[dict[str, str]] = []
    seen_refs: set[str] = set()
    for kind, rel in _file_rel_values(files, MY_SKIN_SAVEDCAR_CLEANUP_REF_KINDS):
        for candidate in _my_skin_ref_candidates(entry, game_root, rel):
            ref = norm_slashes(candidate)
            key = ref.lower()
            if not key or key in seen_refs:
                continue
            seen_refs.add(key)
            rows.append({"kind": kind, "ref": ref, "skinKey": str(entry.get("key") or "")})
    for kind, rel in _restore_ace_entry_extra_owned_refs(entry):
        for candidate in _my_skin_ref_candidates(entry, game_root, rel):
            ref = norm_slashes(candidate)
            key = ref.lower()
            if not key or key in seen_refs:
                continue
            seen_refs.add(key)
            rows.append({"kind": kind, "ref": ref, "skinKey": str(entry.get("key") or "")})
    return rows


def delete_my_skin_savedcars(entry: dict[str, Any], game_root: Path | None = None) -> dict[str, Any]:
    if game_root is None:
        game_root = normalize_game_root(str(entry.get("game_root") or ""))
    ref_rows = _my_skin_savedcar_ref_rows(entry, game_root)
    result: dict[str, Any] = {
        "refs": ref_rows,
        "matches": [],
        "deleted": [],
        "errors": [],
    }
    if not ref_rows:
        return result
    savedcar_hits, scan_errors = _restore_ace_scan_open_savedcars(ref_rows)
    result["matches"] = [
        {
            "path": str(hit.get("path") or ""),
            "guid": str(hit.get("guid") or ""),
            "matchedRefs": hit.get("matchedRefs") or [],
        }
        for hit in savedcar_hits
    ]
    if scan_errors:
        result["errors"] = [str(error) for error in scan_errors]
        return result
    if not savedcar_hits:
        return result
    deleted, delete_errors = _restore_ace_delete_savedcars(savedcar_hits)
    result["deleted"] = deleted
    if delete_errors:
        result["errors"] = [str(error) for error in delete_errors]
    return result


def _unique_quarantine_target(path: Path) -> Path:
    if not path.exists():
        return path
    stem = path.stem
    suffix = path.suffix
    index = 1
    while True:
        candidate = path.with_name(f"{stem}_{index}{suffix}")
        if not candidate.exists():
            return candidate
        index += 1


def _quarantine_external_path(path: Path, source_root: Path, quarantine_root: Path) -> str:
    relative = path.resolve().relative_to(source_root.resolve())
    target = _unique_quarantine_target(quarantine_root / relative)
    target.parent.mkdir(parents=True, exist_ok=True)
    shutil.move(str(path), str(target))
    return norm_slashes(str(target.relative_to(quarantine_root)))


def quarantine_my_skin_savedcars(entry: dict[str, Any], trash_dir: Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(str(entry.get("game_root") or ""))
    ref_rows = _my_skin_savedcar_ref_rows(entry, game_root)
    result: dict[str, Any] = {
        "refs": ref_rows,
        "matches": [],
        "moved": [],
        "errors": [],
        "quarantineDir": "",
    }
    if not ref_rows:
        return result
    savedcar_hits, scan_errors = _restore_ace_scan_open_savedcars(ref_rows)
    result["matches"] = [
        {
            "path": str(hit.get("path") or ""),
            "guid": str(hit.get("guid") or ""),
            "matchedRefs": hit.get("matchedRefs") or [],
        }
        for hit in savedcar_hits
    ]
    if scan_errors:
        result["errors"] = [str(error) for error in scan_errors]
        return result
    if not savedcar_hits:
        return result

    if trash_dir is None:
        stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        skin_id = re.sub(r"[^0-9A-Za-z._-]+", "_", str(entry.get("skin_id") or "skin")).strip("_") or "skin"
        trash_dir = get_user_data_root() / "SavedCarsQuarantine" / f"{stamp}__{skin_id}"
    quarantine_root = Path(trash_dir) / "ace_savedcars"
    quarantine_root.mkdir(parents=True, exist_ok=True)
    result["quarantineDir"] = str(quarantine_root)

    profile_root = saved_games_paths.ace_root() / "ProfileData"
    thumbnail_root = _ace_savedcars_thumbnail_root()
    seen_paths: set[str] = set()
    errors: list[str] = list(result.get("errors") or [])
    moved: list[dict[str, Any]] = []
    for hit in savedcar_hits:
        savedcar = Path(str(hit.get("path") or ""))
        guid = str(hit.get("guid") or "")
        moved_files: list[str] = []
        try:
            if savedcar.exists() and savedcar.is_file():
                key = str(savedcar.resolve()).lower()
                if key not in seen_paths:
                    seen_paths.add(key)
                    moved_files.append(_quarantine_external_path(savedcar, profile_root, quarantine_root / "ProfileData"))
            thumbnail_texture, thumbnail_mips = _ace_savedcar_thumbnail_path_for_guid(guid)
            for thumbnail in (thumbnail_texture, thumbnail_mips):
                if thumbnail and thumbnail.exists() and thumbnail.is_file():
                    key = str(thumbnail.resolve()).lower()
                    if key in seen_paths:
                        continue
                    seen_paths.add(key)
                    moved_files.append(_quarantine_external_path(thumbnail, thumbnail_root, quarantine_root / "Thumbnails"))
            moved.append(
                {
                    "path": str(savedcar),
                    "guid": guid,
                    "movedFiles": moved_files,
                    "matchedRefs": hit.get("matchedRefs") or [],
                }
            )
        except Exception as exc:
            errors.append(f"{savedcar}: {exc}")
    result["moved"] = moved
    result["errors"] = errors
    return result


def _restore_ace_write_bytes_no_backup(path: Path, data: bytes) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_bytes(data)


def _restore_ace_patch_actor_no_backup(
    actor_path: Path,
    cardesign_refs: list[str],
    visual_refs: list[str],
    road_plate_compatible_refs: list[str] | None = None,
) -> dict[str, Any]:
    if not actor_path or not actor_path.exists() or not actor_path.is_file():
        return {"path": str(actor_path) if actor_path else "", "changed": False, "skipped": "actor missing"}
    old_data = actor_path.read_bytes()
    new_data = old_data
    removed_cardesign = 0
    removed_visual = 0
    removed_road_plate = 0
    for ref in cardesign_refs:
        new_data, did_change = remove_cardesign_from_actor_data(new_data, ref)
        removed_cardesign += 1 if did_change else 0
    for ref in visual_refs:
        new_data, did_change = remove_actor_resource_data(new_data, ref, 11)
        removed_visual += 1 if did_change else 0
    for ref in road_plate_compatible_refs or []:
        new_data, did_change = road_plates.remove_actor_resource_data(new_data, ref, road_plates.ACTOR_COMPATIBLE_PART_FIELD)
        removed_road_plate += 1 if did_change else 0
    if new_data != old_data:
        _restore_ace_write_bytes_no_backup(actor_path, new_data)
    return {
        "path": str(actor_path),
        "changed": new_data != old_data,
        "removedCardesignRefs": removed_cardesign,
        "removedVisualRefs": removed_visual,
        "removedRoadPlateRefs": removed_road_plate,
    }


def _restore_ace_patch_mechanical_no_backup(
    mechanical_path: Path,
    visual_refs: list[str],
    road_plate_compatible_refs: list[str] | None = None,
) -> dict[str, Any]:
    if not mechanical_path or not mechanical_path.exists() or not mechanical_path.is_file():
        return {"path": str(mechanical_path) if mechanical_path else "", "changed": False, "skipped": "mechanical missing"}
    old_data = mechanical_path.read_bytes()
    new_data = old_data
    removed_visual = 0
    removed_road_plate = 0
    for ref in visual_refs:
        new_data, did_change = remove_visual_from_mechanicalpreset_data(new_data, ref)
        removed_visual += 1 if did_change else 0
    for ref in road_plate_compatible_refs or []:
        new_data, did_change = road_plates.remove_mechanical_compatiblepart_data(new_data, ref)
        removed_road_plate += 1 if did_change else 0
    if new_data != old_data:
        _restore_ace_write_bytes_no_backup(mechanical_path, new_data)
    return {
        "path": str(mechanical_path),
        "changed": new_data != old_data,
        "removedVisualRefs": removed_visual,
        "removedRoadPlateRefs": removed_road_plate,
    }


def _restore_ace_remove_loc_entry_no_backup(path: Path, key: str) -> dict[str, Any]:
    if not key or not path.exists() or not path.is_file():
        return {"path": str(path), "key": key, "changed": False}
    text = path.read_text(encoding="utf-8", errors="replace")
    lines = text.splitlines()
    kept = [line for line in lines if not line.startswith(f"{key}=")]
    changed = len(kept) != len(lines)
    if changed:
        newline = "\r\n" if "\r\n" in text else "\n"
        out_text = newline.join(kept)
        if kept and text.endswith(("\r\n", "\n")):
            out_text += newline
        path.write_text(out_text, encoding="utf-8")
    return {"path": str(path), "key": key, "changed": changed}


def _restore_ace_delete_game_paths(game_root: Path, rows: list[dict[str, Any]]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    paths = _remove_child_paths([Path(str(row.get("path"))) for row in rows if row.get("path")])
    row_by_path = {str(Path(str(row.get("path"))).resolve()).lower(): row for row in rows if row.get("path")}
    deleted: list[dict[str, Any]] = []
    missing: list[dict[str, Any]] = []
    for path in paths:
        row = row_by_path.get(str(path.resolve()).lower(), {})
        rel = _restore_ace_path_label(game_root, path)
        if not path.exists():
            missing.append({"path": str(path), "rel": rel, "kind": row.get("kind") or ""})
            continue
        if not _restore_ace_path_is_allowed(game_root, path):
            raise ValueError(f"Refusing to delete path outside allowed ACE root content: {path}")
        if path.is_dir():
            shutil.rmtree(path)
            action = "delete_dir"
        elif path.is_file():
            path.unlink()
            action = "delete_file"
        else:
            missing.append({"path": str(path), "rel": rel, "kind": row.get("kind") or ""})
            continue
        deleted.append({"path": str(path), "rel": rel, "kind": row.get("kind") or "", "action": action})
    return deleted, missing


def _restore_ace_extracted_folder_rows(game_root: Path) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    for name in EXTRACTED_TOP_LEVEL_DIR_NAMES:
        path = game_root / name
        if path.exists() and path.is_dir():
            rows.append({"name": name, "path": str(path)})
    return rows


def restore_ace_evo_to_default(root: str | Path) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    if not game_root.exists() or not game_root.is_dir():
        raise FileNotFoundError(f"AC EVO root not found: {game_root}")
    if game_launcher.is_game_running():
        raise RuntimeError("Assetto Corsa EVO is running. Close the game before restoring it to default.")

    package_manifest_root = get_package_state_manifest_root()
    package_before = package_state.content_package_status(game_root, package_manifest_root)
    if not package_before.get("activeExists") and not package_before.get("disabledExists"):
        raise FileNotFoundError(
            "content.kspkg is missing and no LiveryLab-disabled package was found. "
            "Restore cannot safely return AC EVO to the default package state."
        )

    skins = load_my_skins()
    affected_indices: list[int] = []
    entry_plans: list[dict[str, Any]] = []
    for index, entry in enumerate(skins):
        if not isinstance(entry, dict) or not _is_liverylab_managed_my_skin(entry):
            continue
        item_root = normalize_game_root(str(entry.get("game_root") or game_root))
        if item_root != game_root:
            continue
        affected_indices.append(index)
        plan = _restore_ace_collect_entry_plan(entry, game_root)
        plan["deleteRows"].extend(back_to_default.collect_driver_delete_rows(entry, game_root))
        entry_plans.append(plan)
    tyre_delete_rows = back_to_default.collect_tyre_brand_delete_rows(game_root, get_tyre_brands_root())
    grid_paths = grid_bridge.paths(game_root)

    all_savedcar_refs = [row for plan in entry_plans for row in (plan.get("savedcarRefs") or [])]
    savedcar_hits, savedcar_scan_errors = _restore_ace_scan_open_savedcars(all_savedcar_refs)
    if savedcar_scan_errors:
        preview = "\n".join(f"- {row}" for row in savedcar_scan_errors[:12])
        raise RuntimeError(f"SavedCars could not be scanned reliably. No files were removed.\n{preview}")

    root_targets: list[Path] = []
    for plan in entry_plans:
        root_targets.extend([Path(str(row.get("path"))) for row in plan.get("deleteRows") or [] if row.get("path")])
        if plan.get("actorPath"):
            root_targets.append(Path(str(plan.get("actorPath"))))
        root_targets.extend([Path(str(path)) for path in plan.get("mechanicalPaths") or []])
        root_targets.extend([Path(str(path)) for path in plan.get("locPaths") or []])
    root_targets.extend([Path(str(row.get("path"))) for row in tyre_delete_rows if row.get("path")])
    root_targets.extend([grid_paths["ui"], game_root / "content" / "cars", game_root / "system" / "savedcars"])
    disabled_path = package_before.get("disabledPath")
    if disabled_path:
        root_targets.append(Path(str(disabled_path)))
    root_targets.append(game_root / package_state.CONTENT_PACKAGE_NAME)
    require_game_write_ready(game_root, "restore AC EVO to default", root_targets, require_content_cars=False)
    advanced_setup_reset = invalidate_advanced_content_setup(
        game_root,
        "AC EVO was restored to the default package runtime.",
    )

    session_dir = _restore_ace_session_dir()
    manifest: dict[str, Any] = {
        "action": "restore_ace_evo_to_default",
        "startedAt": datetime.now().isoformat(timespec="seconds"),
        "root": str(game_root),
        "backupCreated": False,
        "affectedSkins": [plan.get("entry") for plan in entry_plans],
        "packageBefore": package_before,
        "packageAfter": {},
        "advancedContentSetup": advanced_setup_reset,
        "savedCarsDeleted": [],
        "savedCarDeleteErrors": [],
        "actorPatches": [],
        "mechanicalPatches": [],
        "locChanges": [],
        "gridCleanup": {},
        "aiVehicleSpecs": {},
        "liverylabSafety": {},
        "tyreBrandDeleteRows": [back_to_default.path_label(game_root, Path(str(row.get("path")))) for row in tyre_delete_rows if row.get("path")],
        "gameFilesDeleted": [],
        "gameFilesMissing": [],
        "registryUpdated": False,
        "settingsPath": "",
        "completedAt": "",
    }
    try:
        savedcars_deleted, savedcar_delete_errors = _restore_ace_delete_savedcars(savedcar_hits)
        manifest["savedCarsDeleted"] = savedcars_deleted
        manifest["savedCarDeleteErrors"] = savedcar_delete_errors
        if savedcar_delete_errors:
            manifest["abortedAt"] = datetime.now().isoformat(timespec="seconds")
            manifest["abortReason"] = "SavedCar deletion failed before game files were removed."
            manifest_path = _restore_ace_write_manifest(session_dir, manifest)
            raise RuntimeError(f"SavedCar deletion failed. Game files were not removed. Manifest: {manifest_path}")

        package_result = package_state.restore_content_package(game_root, package_manifest_root)
        manifest["packageAfter"] = package_result
        manifest["gridCleanup"] = back_to_default.cleanup_grid_bridge_no_backup(game_root)
        manifest["aiVehicleSpecs"] = back_to_default.cleanup_ai_vehicle_specs_no_backup(game_root, get_livery_library_root())
        manifest["liverylabSafety"] = package_refresh.cleanup_liverylab_safety_no_backup(game_root)

        seen_actor_paths: set[str] = set()
        seen_mechanical_jobs: set[tuple[str, str]] = set()
        for plan in entry_plans:
            actor_path = plan.get("actorPath")
            if actor_path:
                actor = Path(str(actor_path))
                actor_key = str(actor.resolve()).lower()
                if actor_key not in seen_actor_paths:
                    seen_actor_paths.add(actor_key)
                    related_plans = [
                        row for row in entry_plans
                        if row.get("actorPath") and str(Path(str(row.get("actorPath"))).resolve()).lower() == actor_key
                    ]
                    cardesign_refs = [ref for row in related_plans for ref in (row.get("cardesignRefs") or [])]
                    visual_refs = [ref for row in related_plans for ref in (row.get("visualRefs") or [])]
                    road_plate_refs = [ref for row in related_plans for ref in (row.get("roadPlateCompatibleRefs") or [])]
                    manifest["actorPatches"].append(_restore_ace_patch_actor_no_backup(actor, cardesign_refs, visual_refs, road_plate_refs))
            for mechanical_path in plan.get("mechanicalPaths") or []:
                path = Path(str(mechanical_path))
                visual_refs = [str(ref) for ref in (plan.get("visualRefs") or [])]
                road_plate_refs = [str(ref) for ref in (plan.get("roadPlateCompatibleRefs") or [])]
                job_refs = sorted({ref.lower() for ref in [*visual_refs, *road_plate_refs]})
                job_key = (str(path.resolve()).lower(), "\n".join(job_refs))
                if job_key in seen_mechanical_jobs:
                    continue
                seen_mechanical_jobs.add(job_key)
                manifest["mechanicalPatches"].append(_restore_ace_patch_mechanical_no_backup(path, visual_refs, road_plate_refs))

        seen_loc_jobs: set[tuple[str, str]] = set()
        for plan in entry_plans:
            loc_key = str(plan.get("locKey") or "").strip()
            if not loc_key:
                continue
            for loc_path in plan.get("locPaths") or []:
                path = Path(str(loc_path))
                job_key = (str(path.resolve()).lower(), loc_key)
                if job_key in seen_loc_jobs:
                    continue
                seen_loc_jobs.add(job_key)
                manifest["locChanges"].append(_restore_ace_remove_loc_entry_no_backup(path, loc_key))

        delete_rows = [row for plan in entry_plans for row in (plan.get("deleteRows") or [])] + tyre_delete_rows
        deleted, missing = _restore_ace_delete_game_paths(game_root, delete_rows)
        manifest["gameFilesDeleted"] = deleted
        manifest["gameFilesMissing"] = missing

        for index in affected_indices:
            skins[index]["active"] = False
            _clear_my_skin_game_sync(skins[index])
        saved = save_my_skins(skins) if affected_indices else None
        manifest["registryUpdated"] = bool(saved)
        manifest["settingsPath"] = str(saved) if saved else ""
        manifest["completedAt"] = datetime.now().isoformat(timespec="seconds")
        manifest_path = _restore_ace_write_manifest(session_dir, manifest)
    except Exception:
        if not manifest.get("completedAt"):
            manifest["failedAt"] = datetime.now().isoformat(timespec="seconds")
            try:
                _restore_ace_write_manifest(session_dir, manifest)
            except Exception:
                pass
        raise

    loc_changed = sum(1 for row in manifest["locChanges"] if row.get("changed"))
    actor_changed = sum(1 for row in manifest["actorPatches"] if row.get("changed"))
    mechanical_changed = sum(1 for row in manifest["mechanicalPatches"] if row.get("changed"))
    grid_cleanup = manifest["gridCleanup"] if isinstance(manifest.get("gridCleanup"), dict) else {}
    ai_specs = manifest["aiVehicleSpecs"] if isinstance(manifest.get("aiVehicleSpecs"), dict) else {}
    safety_cleanup = manifest["liverylabSafety"] if isinstance(manifest.get("liverylabSafety"), dict) else {}
    tyre_deleted = sum(1 for row in manifest["gameFilesDeleted"] if str(row.get("kind") or "").startswith("custom_tyre_brand."))
    driver_deleted = sum(1 for row in manifest["gameFilesDeleted"] if str(row.get("kind") or "").startswith("driver_profile."))
    return {
        "root": str(game_root),
        "folder": str(session_dir),
        "manifestPath": str(manifest_path),
        "packageRestored": bool(manifest["packageAfter"].get("renamed")),
        "affectedSkinCount": len(affected_indices),
        "savedCarsDeleted": len(manifest["savedCarsDeleted"]),
        "savedCarThumbnailsDeleted": sum(len(row.get("thumbnails") or []) for row in manifest["savedCarsDeleted"]),
        "gameFilesDeleted": len(manifest["gameFilesDeleted"]),
        "gameFilesMissing": len(manifest["gameFilesMissing"]),
        "locEntriesRemoved": loc_changed,
        "actorFilesPatched": actor_changed,
        "mechanicalFilesPatched": mechanical_changed,
        "gridBridgeRemoved": int(grid_cleanup.get("bridgeWriteCount") or 0) + int(bool(grid_cleanup.get("bridgeDirDeleted"))),
        "nameCleanupRemoved": int(grid_cleanup.get("nameCleanupWriteCount") or 0),
        "aiVehicleSpecsCleaned": int(ai_specs.get("cleanupCount") or 0),
        "liverylabSafetyRemoved": int(safety_cleanup.get("cleanupCount") or 0),
        "tyreBrandFilesDeleted": tyre_deleted,
        "driverPathsDeleted": driver_deleted,
        "extractedFolders": _restore_ace_extracted_folder_rows(game_root),
        "registryUpdated": bool(manifest["registryUpdated"]),
    }


def prepare_game_for_package_refresh(root: str | Path) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    if not game_root.exists() or not game_root.is_dir():
        raise FileNotFoundError(f"AC EVO root not found: {game_root}")
    if game_launcher.is_game_running():
        raise RuntimeError("Assetto Corsa EVO is running. Close the game before preparing package refresh.")

    skins = load_my_skins()
    affected_indices: list[int] = []
    entry_plans: list[dict[str, Any]] = []
    for index, entry in enumerate(skins):
        if not isinstance(entry, dict) or not _is_liverylab_managed_my_skin(entry):
            continue
        item_root = normalize_game_root(str(entry.get("game_root") or game_root))
        if item_root != game_root:
            continue
        affected_indices.append(index)
        plan = _restore_ace_collect_entry_plan(entry, game_root)
        plan["deleteRows"].extend(back_to_default.collect_driver_delete_rows(entry, game_root))
        entry_plans.append(plan)

    tyre_delete_rows = back_to_default.collect_tyre_brand_delete_rows(game_root, get_tyre_brands_root())
    all_savedcar_refs = [row for plan in entry_plans for row in (plan.get("savedcarRefs") or [])]
    savedcar_hits, savedcar_scan_errors = _restore_ace_scan_open_savedcars(all_savedcar_refs)
    if savedcar_scan_errors:
        preview = "\n".join(f"- {row}" for row in savedcar_scan_errors[:12])
        raise RuntimeError(f"SavedCars could not be scanned reliably. No files were removed.\n{preview}")

    root_targets: list[Path] = [game_root / "content" / "cars", game_root / "uiresources", game_root / "system" / "savedcars"]
    for plan in entry_plans:
        root_targets.extend([Path(str(row.get("path"))) for row in plan.get("deleteRows") or [] if row.get("path")])
        if plan.get("actorPath"):
            root_targets.append(Path(str(plan.get("actorPath"))))
        root_targets.extend([Path(str(path)) for path in plan.get("mechanicalPaths") or []])
        root_targets.extend([Path(str(path)) for path in plan.get("locPaths") or []])
    root_targets.extend([Path(str(row.get("path"))) for row in tyre_delete_rows if row.get("path")])
    require_game_write_ready(game_root, "prepare AC EVO package refresh", root_targets, require_content_cars=False)
    advanced_setup_reset = invalidate_advanced_content_setup(
        game_root,
        "AC EVO package refresh preparation requires a new full-content extraction.",
    )

    session_dir = package_refresh.session_dir(get_user_data_root())
    manifest: dict[str, Any] = {
        "action": "prepare_game_for_package_refresh",
        "startedAt": datetime.now().isoformat(timespec="seconds"),
        "root": str(game_root),
        "advancedContentSetup": advanced_setup_reset,
        "affectedSkins": [plan.get("entry") for plan in entry_plans],
        "savedCarsDeleted": [],
        "savedCarDeleteErrors": [],
        "actorPatches": [],
        "mechanicalPatches": [],
        "locChanges": [],
        "runtimeCleanup": {},
        "tyreBrandDeleteRows": [back_to_default.path_label(game_root, Path(str(row.get("path")))) for row in tyre_delete_rows if row.get("path")],
        "gameFilesDeleted": [],
        "gameFilesMissing": [],
        "registryUpdated": False,
        "settingsPath": "",
        "completedAt": "",
    }
    try:
        savedcars_deleted, savedcar_delete_errors = _restore_ace_delete_savedcars(savedcar_hits)
        manifest["savedCarsDeleted"] = savedcars_deleted
        manifest["savedCarDeleteErrors"] = savedcar_delete_errors
        if savedcar_delete_errors:
            manifest["abortedAt"] = datetime.now().isoformat(timespec="seconds")
            manifest["abortReason"] = "SavedCar deletion failed before game files were removed."
            manifest_path = package_refresh.write_manifest(session_dir, manifest)
            raise RuntimeError(f"SavedCar deletion failed. Game files were not removed. Manifest: {manifest_path}")

        manifest["runtimeCleanup"] = package_refresh.cleanup_runtime_hooks_no_backup(game_root, get_livery_library_root())

        seen_actor_paths: set[str] = set()
        seen_mechanical_jobs: set[tuple[str, str]] = set()
        for plan in entry_plans:
            actor_path = plan.get("actorPath")
            if actor_path:
                actor = Path(str(actor_path))
                actor_key = str(actor.resolve()).lower()
                if actor_key not in seen_actor_paths:
                    seen_actor_paths.add(actor_key)
                    related_plans = [
                        row for row in entry_plans
                        if row.get("actorPath") and str(Path(str(row.get("actorPath"))).resolve()).lower() == actor_key
                    ]
                    cardesign_refs = [ref for row in related_plans for ref in (row.get("cardesignRefs") or [])]
                    visual_refs = [ref for row in related_plans for ref in (row.get("visualRefs") or [])]
                    road_plate_refs = [ref for row in related_plans for ref in (row.get("roadPlateCompatibleRefs") or [])]
                    manifest["actorPatches"].append(_restore_ace_patch_actor_no_backup(actor, cardesign_refs, visual_refs, road_plate_refs))
            for mechanical_path in plan.get("mechanicalPaths") or []:
                path = Path(str(mechanical_path))
                visual_refs = [str(ref) for ref in (plan.get("visualRefs") or [])]
                road_plate_refs = [str(ref) for ref in (plan.get("roadPlateCompatibleRefs") or [])]
                job_refs = sorted({ref.lower() for ref in [*visual_refs, *road_plate_refs]})
                job_key = (str(path.resolve()).lower(), "\n".join(job_refs))
                if job_key in seen_mechanical_jobs:
                    continue
                seen_mechanical_jobs.add(job_key)
                manifest["mechanicalPatches"].append(_restore_ace_patch_mechanical_no_backup(path, visual_refs, road_plate_refs))

        seen_loc_jobs: set[tuple[str, str]] = set()
        for plan in entry_plans:
            loc_key = str(plan.get("locKey") or "").strip()
            if not loc_key:
                continue
            for loc_path in plan.get("locPaths") or []:
                path = Path(str(loc_path))
                job_key = (str(path.resolve()).lower(), loc_key)
                if job_key in seen_loc_jobs:
                    continue
                seen_loc_jobs.add(job_key)
                manifest["locChanges"].append(_restore_ace_remove_loc_entry_no_backup(path, loc_key))

        delete_rows = [row for plan in entry_plans for row in (plan.get("deleteRows") or [])] + tyre_delete_rows
        deleted, missing = _restore_ace_delete_game_paths(game_root, delete_rows)
        manifest["gameFilesDeleted"] = deleted
        manifest["gameFilesMissing"] = missing

        for index in affected_indices:
            skins[index]["active"] = False
            _clear_my_skin_game_sync(skins[index])
        saved = save_my_skins(skins) if affected_indices else None
        manifest["registryUpdated"] = bool(saved)
        manifest["settingsPath"] = str(saved) if saved else ""
        manifest["completedAt"] = datetime.now().isoformat(timespec="seconds")
        manifest_path = package_refresh.write_manifest(session_dir, manifest)
    except Exception:
        if not manifest.get("completedAt"):
            manifest["failedAt"] = datetime.now().isoformat(timespec="seconds")
            try:
                package_refresh.write_manifest(session_dir, manifest)
            except Exception:
                pass
        raise

    runtime = manifest["runtimeCleanup"] if isinstance(manifest.get("runtimeCleanup"), dict) else {}
    grid_cleanup = runtime.get("grid") if isinstance(runtime.get("grid"), dict) else {}
    safety_cleanup = runtime.get("liverylabSafety") if isinstance(runtime.get("liverylabSafety"), dict) else {}
    ai_specs = runtime.get("aiVehicleSpecs") if isinstance(runtime.get("aiVehicleSpecs"), dict) else {}
    loc_changed = sum(1 for row in manifest["locChanges"] if row.get("changed"))
    actor_changed = sum(1 for row in manifest["actorPatches"] if row.get("changed"))
    mechanical_changed = sum(1 for row in manifest["mechanicalPatches"] if row.get("changed"))
    tyre_deleted = sum(1 for row in manifest["gameFilesDeleted"] if str(row.get("kind") or "").startswith("custom_tyre_brand."))
    driver_deleted = sum(1 for row in manifest["gameFilesDeleted"] if str(row.get("kind") or "").startswith("driver_profile."))
    return {
        "root": str(game_root),
        "folder": str(session_dir),
        "manifestPath": str(manifest_path),
        "affectedSkinCount": len(affected_indices),
        "savedCarsDeleted": len(manifest["savedCarsDeleted"]),
        "savedCarThumbnailsDeleted": sum(len(row.get("thumbnails") or []) for row in manifest["savedCarsDeleted"]),
        "gameFilesDeleted": len(manifest["gameFilesDeleted"]),
        "gameFilesMissing": len(manifest["gameFilesMissing"]),
        "locEntriesRemoved": loc_changed,
        "actorFilesPatched": actor_changed,
        "mechanicalFilesPatched": mechanical_changed,
        "gridBridgeRemoved": int(grid_cleanup.get("bridgeWriteCount") or 0) + int(bool(grid_cleanup.get("bridgeDirDeleted"))),
        "nameCleanupRemoved": int(grid_cleanup.get("nameCleanupWriteCount") or 0),
        "aiVehicleSpecsCleaned": int(ai_specs.get("cleanupCount") or 0),
        "liverylabSafetyRemoved": int(safety_cleanup.get("cleanupCount") or 0),
        "tyreBrandFilesDeleted": tyre_deleted,
        "driverPathsDeleted": driver_deleted,
        "registryUpdated": bool(manifest["registryUpdated"]),
    }


def get_evo_update_workflow_status(root: str | Path) -> dict[str, Any]:
    return evo_update_workflow.status(get_package_state_manifest_root(), normalize_game_root(root))


def start_evo_update_workflow(root: str | Path) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    active_liveries: list[dict[str, Any]] = []
    for entry in load_my_skins():
        if not isinstance(entry, dict) or entry.get("active") is not True or not _is_liverylab_managed_my_skin(entry):
            continue
        if normalize_game_root(str(entry.get("game_root") or game_root)) != game_root:
            continue
        key = str(entry.get("key") or "").strip()
        if key:
            active_liveries.append({
                "skinKey": key,
                "displayName": str(entry.get("display_name") or entry.get("skin_id") or key),
                "restored": False,
                "error": "",
            })
    return evo_update_workflow.start(get_package_state_manifest_root(), game_root, active_liveries)


def prepare_evo_update_workflow(root: str | Path) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    if not evo_update_workflow.status(get_package_state_manifest_root(), game_root).get("workflow"):
        start_evo_update_workflow(game_root)
    preparation = prepare_game_for_package_refresh(game_root)
    return evo_update_workflow.mark_prepared(get_package_state_manifest_root(), game_root, preparation)


def inspect_evo_update_cleanup(root: str | Path) -> dict[str, Any]:
    return evo_update_workflow.inspect_cleanup(get_package_state_manifest_root(), normalize_game_root(root))


def mark_evo_update_extracted(root: str | Path) -> dict[str, Any]:
    return evo_update_workflow.mark_extracted(get_package_state_manifest_root(), normalize_game_root(root))


def complete_evo_update_workflow(root: str | Path, target_workflow: str, restored_liveries: list[dict[str, Any]] | None = None) -> dict[str, Any]:
    return evo_update_workflow.complete(
        get_package_state_manifest_root(),
        normalize_game_root(root),
        target_workflow,
        restored_liveries,
    )


def update_my_skin(
    skin_key: str,
    display_name: str | None = None,
    preview_image: str | Path | None = None,
    *,
    author: str | None = None,
    description: str | None = None,
    project_metadata_update: bool = False,
) -> dict[str, Any]:
    entry, skins, index = _get_my_skin_entry(skin_key)
    policy = skin_policy.policy_for_skin(entry)
    current_display_name = str(entry.get("display_name") or "").strip()
    requested_display_name = str(display_name or "").strip()
    next_display_name = livery_identity.normalize_display_name(requested_display_name, current_display_name or "Skin") if requested_display_name else current_display_name
    preview_path = Path(preview_image) if preview_image else None
    if next_display_name != current_display_name:
        if livery_identity.identity_locked(entry):
            raise ValueError("This livery name is locked after creation to keep project folders, exports and technical IDs consistent.")
        _assert_my_skin_policy(entry, "canEditMetadata")
    if preview_path:
        _assert_my_skin_policy(entry, "canEditPreviewImage")
    if project_metadata_update:
        _assert_my_skin_policy(entry, "canEditProjectMetadata")
    actions: list[str] = []
    changed = False
    game_sync_changed = False

    def apply_project_metadata_update() -> bool:
        metadata = dict(entry.get("metadata") or {}) if isinstance(entry.get("metadata"), dict) else {}
        current_author = str(metadata.get("author") or "").strip()
        current_description = str(metadata.get("description") or "").strip()
        next_author = str(author if author is not None else current_author).strip()
        next_description = str(description if description is not None else current_description).strip()[:96].strip()
        if next_author == current_author and next_description == current_description:
            return False
        if next_author:
            metadata["author"] = next_author
        else:
            metadata.pop("author", None)
        if next_description:
            metadata["description"] = next_description
        else:
            metadata.pop("description", None)
        if metadata:
            entry["metadata"] = metadata
        else:
            entry.pop("metadata", None)
        if _is_official_external_my_skin_entry(entry):
            details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
            official_metadata = dict(details.get("metadata") or {}) if isinstance(details.get("metadata"), dict) else {}
            if next_author:
                official_metadata["author"] = next_author
            else:
                official_metadata.pop("author", None)
            if next_description:
                official_metadata["description"] = next_description
            else:
                official_metadata.pop("description", None)
            details["metadata"] = official_metadata
            entry["official_external"] = details
            project_folder = _livery_project_folder_for_entry(entry)
            manifest_path = project_folder / official_external_projects.PROJECT_MANIFEST
            if manifest_path.exists() and manifest_path.is_file():
                try:
                    manifest_data = json.loads(manifest_path.read_text(encoding="utf-8"))
                    manifest = manifest_data if isinstance(manifest_data, dict) else {}
                except Exception:
                    manifest = {}
                manifest["metadata"] = official_metadata
                manifest["updatedAt"] = datetime.now().isoformat(timespec="seconds")
                manifest_path.write_text(json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False), encoding="utf-8")
                actions.append("official project manifest metadata updated")
        actions.append("project metadata updated")
        return True

    metadata_only = project_metadata_update and not preview_path and next_display_name == current_display_name
    if metadata_only:
        changed = apply_project_metadata_update()
        skins[index] = entry
        saved = save_my_skins(skins)
        return {
            "item": entry,
            "changed": changed,
            "actions": actions,
            "settingsPath": saved,
            "writer": {
                "operation": "update My Skin project metadata",
                "root": str(_entry_local_root(entry) or entry.get("local_root") or ""),
                "preflight": {},
                "plan": [],
                "results": [],
            },
        }
    game_root = normalize_game_root(str(entry.get("game_root", "")))
    if not game_root.exists():
        raise FileNotFoundError(f"Game root not found: {game_root}")
    local_root = _entry_local_root(entry)
    if local_root is None and not (preview_path and policy.get("readOnly")):
        raise RuntimeError(
            "This skin has no local LiveryLab storage. Skin edits are only allowed in LocalSkins; "
            "use Activate in Game for game-file writes."
        )

    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    if not next_display_name:
        raise ValueError("Display name is empty")

    def local_skin_file(rel_path: str | None) -> Path | None:
        if local_root is None:
            return None
        rel_norm = normalize_skin_asset_rel_path(str(rel_path or ""))
        if not rel_norm:
            return None
        return local_root / Path(*split_content_path(rel_norm))

    writer = None
    if local_root is not None and not policy.get("readOnly"):
        writer = GameFileWriter(
            local_root,
            "update My Skin",
            [
                path
                for path in (
                    local_skin_file(str(files.get("design") or "")),
                    local_skin_file(str(files.get("visual_preset") or "")),
                    local_skin_file(str(files.get("skin_folder") or "")),
                )
                if path
            ],
        )
    if next_display_name != current_display_name:
        design_path = local_skin_file(str(files.get("design") or ""))
        if design_path and design_path.exists():
            old_data = design_path.read_bytes()
            new_data = edit_design_data(old_data, display_name=next_display_name)
            if new_data != old_data:
                _write_game_bytes(design_path, new_data, writer, label="design display name")
                changed = True
                game_sync_changed = True
                actions.append("design display name updated")

        visual_path = local_skin_file(str(files.get("visual_preset") or ""))
        if visual_path and visual_path.exists():
            did_change, _backup = update_visualpreset_file(visual_path, name=next_display_name, presentation=next_display_name, writer=writer)
            changed = changed or did_change
            game_sync_changed = game_sync_changed or did_change
            actions.append(f"visual preset display name {'updated' if did_change else 'already current'}")

        car_content_path = str(entry.get("car_content_path") or "").strip()
        entry["display_name"] = next_display_name
        entry["visual_loc_key"] = make_visual_loc_key(car_content_path, next_display_name)
        game_sync_changed = True
        actions.append("local skin display name updated")

    if preview_path:
        previous_preview_source = _my_skin_preview_source(entry)
        thumbnail_sync = sync_livery_project_thumbnail_to_local_skin(entry, preview_image=preview_path, writer=writer)
        files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else files
        preview_changed = bool(thumbnail_sync.get("changed") or previous_preview_source != MY_SKIN_PREVIEW_SOURCE_CUSTOM)
        changed = changed or preview_changed
        game_sync_changed = game_sync_changed or (preview_changed and not _is_official_external_my_skin_entry(entry))
        entry["preview_source"] = MY_SKIN_PREVIEW_SOURCE_CUSTOM
        if policy.get("readOnly"):
            actions.append("read-only preview override updated")
            actions.append("original package thumbnail preserved")
        else:
            actions.append("project preview PNG updated")
            actions.append("local preview PNG synced from LiveryProjects")

    if project_metadata_update:
        changed = apply_project_metadata_update() or changed

    if game_sync_changed and bool(entry.get("active")):
        _mark_my_skin_game_sync_required(entry, "Local skin metadata changed", "skin_edit")

    skins[index] = entry
    saved = save_my_skins(skins)
    return {
        "item": entry,
        "changed": changed,
        "actions": actions,
        "settingsPath": saved,
        "writer": writer.to_dict() if writer else {},
    }


def use_my_skin_ace_snapshot_thumbnail(skin_key: str) -> dict[str, Any]:
    entry, skins, index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canEditPreviewImage")
    policy = skin_policy.policy_for_skin(entry)
    game_root = normalize_game_root(str(entry.get("game_root", "")))
    if not game_root.exists():
        raise FileNotFoundError(f"Game root not found: {game_root}")
    local_root = _entry_local_root(entry)
    if local_root is None and not policy.get("readOnly"):
        raise RuntimeError(
            "This skin has no local LiveryLab storage. Preview thumbnails are saved into LocalSkins; "
            "use Activate in Game for game-file writes."
        )
    ace_snapshot = _ace_savedcar_thumbnail_for_skin_entry(entry, game_root)
    texture_path = str(ace_snapshot.get("thumbnail_texture_path") or "").strip()
    if not ace_snapshot.get("available") or not texture_path:
        raise FileNotFoundError(str(ace_snapshot.get("message") or "No AC EVO thumbnail is available for this skin"))

    official_external = _is_official_external_my_skin_entry(entry)
    official_project = bool(
        official_external
        and official_external_workflow.role_for_entry(entry) == official_external_workflow.ROLE_PROJECT
    )
    writer = (
        GameFileWriter(local_root, "use AC EVO thumbnail for My Skin", [local_root])
        if local_root is not None and not policy.get("readOnly") and not official_project
        else None
    )
    temp_path = None
    try:
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as handle:
            temp_path = Path(handle.name)
        source_signature: dict[str, Any] = {}
        converted: dict[str, Any] = {}
        for _attempt in range(3):
            before = ace_savedcar_thumbnail_content_signature(texture_path)
            if not before.get("stable"):
                continue
            converted = _ace_savedcar_raw_thumbnail_to_png(texture_path, temp_path)
            after = ace_savedcar_thumbnail_content_signature(texture_path)
            if before.get("signature") == after.get("signature") and after.get("stable"):
                source_signature = after
                break
        if not source_signature:
            raise RuntimeError("AC EVO thumbnail was still being written. Wait a moment and click Refresh again.")
        preview_info = inspect_preview_image(temp_path)
        if not preview_info.get("valid"):
            width = int(preview_info.get("width") or 0)
            height = int(preview_info.get("height") or 0)
            if width <= 0 or height <= 0 or width != height:
                raise RuntimeError(f"AC EVO thumbnail must be square, got {width}x{height}")
            temp_path.write_bytes(_png_bytes_for_skin_thumbnail(temp_path, allow_resize=True))
            converted["normalizedWidth"] = 1024
            converted["normalizedHeight"] = 1024
            converted["originalWidth"] = width
            converted["originalHeight"] = height
        thumbnail_sync = sync_livery_project_thumbnail_to_local_skin(entry, preview_image=temp_path, writer=writer)
    finally:
        if temp_path is not None:
            try:
                temp_path.unlink(missing_ok=True)
            except Exception:
                pass

    previous_source = _my_skin_preview_source(entry)
    changed = bool(thumbnail_sync.get("changed") or previous_source != MY_SKIN_PREVIEW_SOURCE_ACE)
    entry["preview_source"] = MY_SKIN_PREVIEW_SOURCE_ACE
    entry["ace_snapshot_preview"] = {
        "guid": ace_snapshot.get("guid") or "",
        "saved_car_path": ace_snapshot.get("saved_car_path") or "",
        "thumbnail_texture_path": texture_path,
        "source_signature": source_signature.get("signature") or "",
        "source_texture_sha256": (source_signature.get("texture") or {}).get("sha256") or "",
        "source_mips_sha256": (source_signature.get("mips") or {}).get("sha256") or "",
        "updated_at": datetime.now().isoformat(timespec="seconds"),
    }
    actions = []
    if converted.get("originalWidth") and converted.get("originalHeight"):
        actions.append(
            f"AC EVO thumbnail normalized from {converted.get('originalWidth')}x{converted.get('originalHeight')} to 1024x1024"
        )
    actions.append(
        "read-only preview override updated from AC EVO thumbnail"
        if policy.get("readOnly")
        else "project preview PNG updated from AC EVO thumbnail"
    )
    if official_project:
        preview_storage_action = "Official project preview PNG updated"
    elif policy.get("readOnly"):
        preview_storage_action = "LiveryLab read-only preview cache updated"
    elif local_root is not None:
        preview_storage_action = "local preview PNG synced from AC EVO thumbnail"
    else:
        preview_storage_action = "LiveryLab preview cache updated from AC EVO thumbnail"
    actions.append(preview_storage_action)
    game_thumbnail_update: dict[str, Any] = {}
    game_writer: GameFileWriter | None = None
    if bool(entry.get("active")) and not official_external:
        car_model = split_content_path(str(entry.get("car_content_path") or "content\\cars\\car"))[-1]
        required_paths: list[Path] = [game_root / "content" / "cars" / car_model / "generated" / "thumbnails"]
        files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
        visual_rel = normalize_game_rel_path(str(files.get("visual_preset") or ""))
        actor_path = _disk_from_game_rel(game_root, normalize_game_rel_path(str(files.get("actor") or "")))
        mechanical_rels = _target_mechanical_preset_refs_for_entry(entry, game_root, actor_path)
        if not mechanical_rels and str(files.get("mechanical_preset") or "").strip():
            mechanical_rels = [normalize_game_rel_path(str(files.get("mechanical_preset") or ""))]
        if visual_rel:
            for mechanical_rel in mechanical_rels:
                target = generated_thumbnail_disk_path(game_root, str(entry.get("car_content_path") or ""), mechanical_rel, visual_rel)
                required_paths.extend([target, target.with_suffix(".texturemips")])
        game_writer = GameFileWriter(game_root, "use AC EVO thumbnail for game previews", required_paths)
        game_thumbnail_update = sync_livery_project_thumbnail_to_game_previews(entry, game_root, game_writer)
        changed = changed or bool(game_thumbnail_update.get("changed"))
        if game_thumbnail_update.get("changed"):
            actions.append("EVO generated preview thumbnails updated")
        elif game_thumbnail_update.get("status") == "current":
            actions.append("EVO generated preview thumbnails already current")
        elif game_thumbnail_update.get("skipped"):
            _mark_my_skin_game_sync_required(entry, "AC EVO generated preview thumbnail update skipped", "ace_preview_thumbnail")
            actions.append(f"EVO generated preview thumbnails skipped: {game_thumbnail_update.get('skipped')}")
    elif official_external:
        game_thumbnail_update = {
            "changed": False,
            "status": "not_applicable",
            "skipped": "Official external livery previews stay in LiveryLab-managed storage and Saved Games",
        }

    skins[index] = entry
    saved = save_my_skins(skins)
    return {
        "item": entry,
        "changed": changed,
        "actions": actions,
        "settingsPath": saved,
        "thumbnailSync": thumbnail_sync,
        "gameThumbnailUpdate": game_thumbnail_update,
        "aceSnapshot": ace_snapshot,
        "converted": converted,
        "writer": writer.to_dict() if writer else {},
        "gameWriter": game_writer.to_dict() if game_writer else {},
    }


def mark_my_skin_game_sync_required(skin_key: str, reason: str, workflow: str = "") -> dict[str, Any]:
    entry, skins, index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canUseEditablePreviewTools")
    actions: list[str] = []
    if bool(entry.get("active")):
        _mark_my_skin_game_sync_required(entry, reason or "Local skin changed", workflow or "local_change")
        actions.append("game update required")
    skins[index] = entry
    saved = save_my_skins(skins)
    return {
        "item": entry,
        "changed": bool(actions),
        "actions": actions,
        "settingsPath": saved,
    }


def update_my_skin_tyre_brand(skin_key: str, brand: str) -> dict[str, Any]:
    entry, skins, index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canEditTyres")
    game_root = _resolve_my_skin_game_root(entry)
    entry["game_root"] = str(game_root)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    visual_rel = str(files.get("visual_preset") or "").strip()
    if not visual_rel:
        raise FileNotFoundError("Visual preset for this skin is not registered")
    source_visual_path = _skin_file_disk_path(game_root, entry, visual_rel)
    if source_visual_path is None or not source_visual_path.exists():
        raise FileNotFoundError("Visual preset for this skin was not found")
    visual_path = _ensure_local_skin_file_copy(game_root, entry, visual_rel, source=source_visual_path)
    if visual_path is None or not visual_path.exists():
        raise FileNotFoundError("Local visual preset for this skin could not be prepared")

    local_root = _entry_local_root(entry)
    writer_root = local_root or visual_path.parent
    writer = GameFileWriter(writer_root, "update My Skin tyre brand", [visual_path])
    old_data = visual_path.read_bytes()
    tyre_info = describe_my_skin_tyre_brand(entry, game_root)
    available_options = list(tyre_info.get("availableOptions") or [])
    brand_key = str(brand or "default").strip().lower()
    if brand_key not in {"", "default", "from_car", "car"} and not bool(tyre_info.get("supported")):
        raise ValueError(str(tyre_info.get("supportMessage") or "Tyre brand selection is not supported for this skin."))
    new_data = edit_visualpreset_tyre_brand_data(old_data, brand, available_options)
    requested_selection = _canonical_tyre_brand_selection(brand, available_options)
    verified_selection = _tyre_brand_selection_from_visual_data(new_data, available_options)
    if verified_selection != requested_selection:
        raise RuntimeError(
            "Tyre brand selection could not be verified in the local visual preset: "
            f"expected {requested_selection}, found {verified_selection}"
        )
    changed = new_data != old_data
    backup = None
    actions: list[str] = []
    if changed:
        backup = _write_game_bytes(visual_path, new_data, writer, label="visual preset tyre brand")
        _mark_my_skin_game_sync_required(entry, "Tyre brand changed", "update_my_skin_tyre_brand")
        actions.append(f"local visual preset tyre brand updated (backup: {backup or '-'})")
    else:
        actions.append("local visual preset tyre brand already current")

    persisted_info = describe_my_skin_tyre_brand(entry, game_root)
    entry["tyre_brand"] = _json_ready(
        _persisted_tyre_brand_selection(persisted_info, requested_selection, available_options)
    )
    skins[index] = entry
    saved = save_my_skins(skins)
    return {
        "item": entry,
        "changed": changed,
        "actions": actions,
        "backup": backup,
        "settingsPath": saved,
        "writer": writer.to_dict(),
    }


def list_driver_profiles(car_model: str | None = None) -> dict[str, Any]:
    result = driver_library.list_driver_profiles(get_livery_library_root(), car_model=car_model)
    result["driverSupport"] = driver_library.driver_support_info(car_model=car_model)
    if car_model and not result["driverSupport"]["supported"]:
        result["items"] = []
        result["count"] = 0
    items = result.get("items") if isinstance(result.get("items"), list) else []
    result["items"] = [
        _attach_driver_profile_usage(driver_projects.attach_project_info(get_livery_projects_root(), item))
        for item in items
        if isinstance(item, dict)
    ]
    result["driverProjectsRoot"] = str(get_driver_projects_root(create=False))
    return _json_ready(result)


def list_custom_grids() -> dict[str, Any]:
    return _json_ready(grid_presets.list_grid_presets(get_livery_library_root()))


def create_custom_grid(payload: dict[str, Any] | None = None) -> dict[str, Any]:
    return _json_ready(grid_presets.create_grid_preset(get_livery_library_root(), payload or {}))


def save_custom_grid(payload: dict[str, Any]) -> dict[str, Any]:
    return _json_ready(grid_presets.save_grid_preset(get_livery_library_root(), payload or {}))


def save_custom_grid_preview_image(payload: dict[str, Any]) -> dict[str, Any]:
    data = payload or {}
    grid_id = str(data.get("id") or "").strip()
    preview_png = str(data.get("previewPng") or data.get("preview_png") or "").strip()
    if not grid_id:
        raise ValueError("Grid preset id is required")
    if not preview_png:
        raise ValueError("Grid preview PNG is required")
    return _json_ready(grid_presets.update_grid_preset_preview_image(get_livery_library_root(), grid_id, preview_png))


def duplicate_custom_grid(grid_id: str) -> dict[str, Any]:
    return _json_ready(grid_presets.duplicate_grid_preset(get_livery_library_root(), grid_id))


def delete_custom_grid(grid_id: str) -> dict[str, Any]:
    return _json_ready(grid_presets.delete_grid_preset(get_livery_library_root(), grid_id))


def _custom_grid_package_clean_id(value: Any, fallback: str = "custom_grid") -> str:
    text = sanitize_slug(str(value or fallback))
    return text[:80] or fallback


def _custom_grid_package_member_path(package_root: Path, rel_path: Any, label: str) -> Path:
    raw_text = str(rel_path or "").strip()
    raw_unix = raw_text.replace("\\", "/")
    if re.match(r"^[A-Za-z]:", raw_unix) or raw_unix.startswith("/"):
        raise ValueError(f"Package {label} path is unsafe: {raw_text}")
    parts = [part for part in raw_unix.split("/") if part]
    if not parts:
        raise ValueError(f"Package {label} path is missing")
    rel_disk = Path(*parts)
    if rel_disk.is_absolute() or ".." in rel_disk.parts:
        raise ValueError(f"Package {label} path is unsafe: {raw_text}")
    path = (package_root / rel_disk).resolve()
    try:
        path.relative_to(package_root.resolve())
    except ValueError as exc:
        raise ValueError(f"Package {label} path escapes the package: {raw_text}") from exc
    return path


def _custom_grid_package_preset(value: dict[str, Any], new_id: str | None = None) -> dict[str, Any]:
    grid = dict(value or {})
    if new_id:
        grid["id"] = _custom_grid_package_clean_id(new_id)
    grid.pop("previewPngPath", None)
    entries = grid.get("entries") if isinstance(grid.get("entries"), list) else []
    clean_entries: list[dict[str, Any]] = []
    for entry in entries:
        if not isinstance(entry, dict):
            continue
        row = dict(entry)
        row.pop("thumbnailPngDiskPath", None)
        row.pop("thumbnail_png_disk_path", None)
        clean_entries.append(row)
    grid["entries"] = clean_entries
    normalized = grid_presets._normalize_preset(grid)
    normalized.pop("previewPngPath", None)
    for entry in normalized.get("entries") or []:
        if isinstance(entry, dict):
            entry.pop("thumbnailPngDiskPath", None)
    return _json_ready(normalized)


def _custom_grid_package_unique_id(items: list[dict[str, Any]], preferred: str) -> str:
    existing = {str(item.get("id") or "").strip().lower() for item in items if isinstance(item, dict)}
    base = _custom_grid_package_clean_id(preferred)
    candidate = base
    index = 2
    while candidate.lower() in existing:
        candidate = f"{base}_import" if index == 2 else f"{base}_import_{index}"
        index += 1
    return candidate


def _custom_grid_package_preview_name(grid_id: str) -> str:
    return f"{_custom_grid_package_clean_id(grid_id, 'grid')}.png"


def export_custom_grid_package(grid_id: str, out_dir: str | Path | None = None, root: str | Path | None = None) -> dict[str, Any]:
    clean_id = str(grid_id or "").strip()
    if not clean_id:
        raise ValueError("Grid preset id is required")
    library_root = get_livery_library_root()
    grids = grid_presets.list_grid_presets(library_root)
    items = grids.get("items") if isinstance(grids.get("items"), list) else []
    grid = next((item for item in items if str(item.get("id") or "").lower() == clean_id.lower()), None)
    if not isinstance(grid, dict):
        raise FileNotFoundError(f"Grid preset not found: {clean_id}")

    package_name = short_export_package_name("grid", grid.get("name") or grid.get("id") or clean_id, grid.get("id") or clean_id)
    export_root = _custom_grid_package_export_root(out_dir, root)
    package_root = export_root / package_name
    if package_root.exists():
        shutil.rmtree(package_root)
    package_root.mkdir(parents=True, exist_ok=True)

    package_grid = _custom_grid_package_preset(grid)
    preview_rel = ""
    preview_source = Path(str(grid.get("previewPngPath") or ""))
    if preview_source.is_file():
        preview_name = _custom_grid_package_preview_name(package_grid.get("id") or package_name)
        preview_target = package_root / "preview" / preview_name
        preview_target.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(preview_source, preview_target)
        package_grid["previewPng"] = preview_name
        preview_rel = normalize_game_rel_path(str(preview_target.relative_to(package_root)))
    else:
        package_grid["previewPng"] = Path(str(package_grid.get("previewPng") or "")).name.strip()

    grid_path = package_root / "grid.json"
    grid_path.write_text(json.dumps(_json_ready(package_grid), indent=2, ensure_ascii=False), encoding="utf-8")
    package_manifest = {
        "formatVersion": 1,
        "type": CUSTOM_GRID_PACKAGE_TYPE,
        "app": {"name": APP_NAME, "version": APP_VERSION},
        "exportedAt": datetime.now().isoformat(timespec="seconds"),
        "grid": {
            "id": package_grid.get("id") or package_name,
            "name": package_grid.get("name") or package_name,
            "path": "grid.json",
            "preview": preview_rel,
        },
        "entryCount": len(package_grid.get("entries") or []),
    }
    manifest_path = package_root / "manifest.json"
    manifest_path.write_text(json.dumps(_json_ready(package_manifest), indent=2, ensure_ascii=False), encoding="utf-8")
    zip_path = package_zip.export_zip_path(export_root, package_name)
    package_zip.zip_directory(package_root, zip_path)
    shutil.rmtree(package_root, ignore_errors=True)
    return {
        "packageRoot": str(zip_path),
        "packagePath": str(zip_path),
        "manifestPath": "manifest.json",
        "grid": package_manifest["grid"],
        "entryCount": package_manifest["entryCount"],
        "zip": True,
    }


def _inspect_custom_grid_package_root(package_root: Path, package_path: str | Path | None = None) -> dict[str, Any]:
    validate_package_import_structure(
        package_root,
        {"manifest.json", "grid.json", "preview"},
        "Custom grid package",
    )
    manifest_path = package_root / "manifest.json"
    if not manifest_path.exists():
        raise FileNotFoundError(f"manifest.json not found in package folder: {package_root}")
    manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    if not isinstance(manifest, dict) or manifest.get("type") != CUSTOM_GRID_PACKAGE_TYPE:
        raise ValueError("Custom grid package manifest is invalid")
    grid_info = manifest.get("grid") if isinstance(manifest.get("grid"), dict) else {}
    grid_rel = str(grid_info.get("path") or "grid.json").strip()
    grid_path = _custom_grid_package_member_path(package_root, grid_rel, "grid")
    missing: list[dict[str, Any]] = []
    grid_manifest: dict[str, Any] = {}
    if not grid_path.exists():
        missing.append({"kind": "grid", "path": grid_rel})
    else:
        try:
            grid_manifest = json.loads(grid_path.read_text(encoding="utf-8"))
        except Exception:
            grid_manifest = {}
        if not isinstance(grid_manifest, dict):
            missing.append({"kind": "grid", "path": grid_rel, "reason": "invalid JSON"})
    preview_rel = str(grid_info.get("preview") or "").strip()
    if preview_rel:
        preview_path = _custom_grid_package_member_path(package_root, preview_rel, "preview")
        if not preview_path.exists():
            missing.append({"kind": "preview", "path": preview_rel})
    entries = grid_manifest.get("entries") if isinstance(grid_manifest.get("entries"), list) else []
    installed = any(
        str(item.get("id") or "").strip().lower() == str((grid_manifest or {}).get("id") or grid_info.get("id") or "").strip().lower()
        for item in (grid_presets.list_grid_presets(get_livery_library_root()).get("items") or [])
        if isinstance(item, dict)
    )
    return {
        "packageRoot": str(package_root),
        "manifestPath": str(manifest_path),
        "manifest": manifest,
        "grid": {**grid_info, "manifest": grid_manifest},
        "entryCount": len(entries),
        "missing": missing,
        "missingCount": len(missing),
        "installed": installed,
        "packagePath": str(package_path or package_root),
        "zip": package_zip.is_zip_package(package_path or ""),
    }


def inspect_custom_grid_package(package_dir: str | Path) -> dict[str, Any]:
    source = Path(package_dir).expanduser()
    with package_zip.package_root(source, get_package_import_temp_root(), allow_folder=True) as package_root:
        return _inspect_custom_grid_package_root(package_root, source)


def import_custom_grid_package(package_dir: str | Path, conflict_mode: str = "rename") -> dict[str, Any]:
    source_package = Path(package_dir).expanduser()
    with package_zip.package_root(source_package, get_package_import_temp_root(), allow_folder=True) as package_root:
        return _import_custom_grid_package_root(package_root, source_package, conflict_mode)


def _import_custom_grid_package_root(package_root: Path, source_package: Path, conflict_mode: str = "rename") -> dict[str, Any]:
    info = _inspect_custom_grid_package_root(package_root, source_package)
    if info.get("missingCount"):
        raise FileNotFoundError("Custom grid package is incomplete")
    grid_info = info.get("grid") if isinstance(info.get("grid"), dict) else {}
    grid_manifest = grid_info.get("manifest") if isinstance(grid_info.get("manifest"), dict) else {}
    if not isinstance(grid_manifest, dict) or not grid_manifest:
        raise ValueError("Custom grid manifest is invalid")

    library_root = get_livery_library_root()
    existing_items = grid_presets.list_grid_presets(library_root).get("items") or []
    old_id = _custom_grid_package_clean_id(grid_manifest.get("id") or grid_info.get("id") or "grid")
    replace_existing = str(conflict_mode or "rename").strip().lower() in {"replace", "overwrite"}
    target_id = old_id if replace_existing else _custom_grid_package_unique_id(existing_items, old_id)
    imported_grid = _custom_grid_package_preset(grid_manifest, target_id)

    preview_rel = str(grid_info.get("preview") or "").strip()
    imported_preview = ""
    if preview_rel:
        preview_source = _custom_grid_package_member_path(package_root, preview_rel, "preview")
        if preview_source.exists():
            preview_target = grid_presets.previews_root(library_root) / _custom_grid_package_preview_name(target_id)
            copy_package_import_file(preview_source, preview_target, "custom grid package preview")
            imported_grid["previewPng"] = preview_target.name
            imported_preview = str(preview_target)

    save_result = grid_presets.save_grid_preset(library_root, imported_grid)
    item = save_result.get("item") if isinstance(save_result.get("item"), dict) else imported_grid
    return _json_ready({
        **save_result,
        "grid": item,
        "gridId": target_id,
        "sourceGridId": old_id,
        "entryCount": len(item.get("entries") or []),
        "importedPreview": imported_preview,
        "conflictMode": "replace" if replace_existing else "rename",
        "packagePath": str(source_package),
        "zip": package_zip.is_zip_package(source_package),
    })


def get_custom_grid_bridge_status(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    status = grid_bridge.bridge_status(game_root, get_custom_grid_gamefile_backup_root())
    status["aiVehicleSpecs"] = ai_vehicle_spec.status(get_livery_library_root())
    return _json_ready(status)


def _unique_destination_path(path: Path) -> Path:
    if not path.exists():
        return path
    index = 2
    while True:
        candidate = path.with_name(f"{path.name}_{index}")
        if not candidate.exists():
            return candidate
        index += 1


def quarantine_custom_grid_ui_backup_dirs(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    ui_root = game_root / "uiresources"
    backup_root = get_custom_grid_gamefile_backup_root() / "Quarantine" / datetime.now().strftime("%Y%m%d_%H%M%S")
    candidates = [
        ui_root / "_acevo_backups",
        ui_root / "js" / "_acevo_backups",
        ui_root / "localization" / "_acevo_backups",
    ]
    moved: list[dict[str, str]] = []
    for source in candidates:
        if not source.exists():
            continue
        try:
            resolved_source = source.resolve()
            resolved_ui = ui_root.resolve()
            if not _path_is_within(resolved_source, resolved_ui):
                raise RuntimeError(f"Refusing to move path outside uiresources: {source}")
            rel = resolved_source.relative_to(game_root.resolve())
            target = _unique_destination_path(backup_root / rel)
            target.parent.mkdir(parents=True, exist_ok=True)
            shutil.move(str(resolved_source), str(target))
            moved.append({"from": str(resolved_source), "to": str(target)})
        except Exception as exc:
            moved.append({"from": str(source), "error": str(exc)})
    if moved:
        backup_root.mkdir(parents=True, exist_ok=True)
        (backup_root / "quarantine_manifest.json").write_text(
            json.dumps({"moved": moved, "createdAt": datetime.now().isoformat(timespec="seconds")}, indent=2, ensure_ascii=False),
            encoding="utf-8",
        )
    return {
        "backupRoot": str(backup_root),
        "movedCount": sum(1 for row in moved if not row.get("error")),
        "errorCount": sum(1 for row in moved if row.get("error")),
        "moved": moved,
    }


def _require_loose_content_for_ui_hooks(game_root: Path, action: str) -> None:
    package_status = package_state.content_package_status(game_root, get_package_state_manifest_root())
    if package_status.get("activeExists"):
        raise RuntimeError(
            f"{action} is blocked while content.kspkg is active. "
            "Disable or rename content.kspkg first so AC EVO uses the extracted loose UI resources. "
            "Mixing packed UI resources with loose LiveryLab UI hooks can crash AC EVO."
        )


def get_multiplayer_safety_status(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    status = multiplayer_safety.status(game_root, get_multiplayer_safety_gamefile_backup_root())
    status["bypassed"] = liverylab_safety_bypassed()
    if status["bypassed"] and not status.get("installed"):
        status["label"] = "Bypassed"
    return _json_ready(status)


def repair_multiplayer_safety_hook(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    if liverylab_safety_bypassed():
        return {
            "skipped": True,
            "bypassed": True,
            "status": {"bypassed": True, "installed": False, "label": "Bypassed"},
            "unsafeSkins": [],
        }
    _require_loose_content_for_ui_hooks(game_root, "Repairing LiveryLab Safety")
    unsafe_skin_items = list_my_skins(game_root, include_hidden=True)
    return _json_ready(
        multiplayer_safety.install_or_repair(
            game_root,
            unsafe_skin_items,
            get_multiplayer_safety_gamefile_backup_root(),
        )
    )


def refresh_multiplayer_safety_manifest(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    if liverylab_safety_bypassed():
        return {
            "skipped": True,
            "bypassed": True,
            "status": {"bypassed": True, "installed": False, "label": "Bypassed"},
            "unsafeSkins": [],
        }
    _require_loose_content_for_ui_hooks(game_root, "Refreshing LiveryLab Safety")
    unsafe_skin_items = list_my_skins(game_root, include_hidden=True)
    return _json_ready(
        multiplayer_safety.refresh_unsafe_skins(
            game_root,
            unsafe_skin_items,
            get_multiplayer_safety_gamefile_backup_root(),
        )
    )


def remove_liverylab_safety_hook(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    return _json_ready(package_refresh.cleanup_liverylab_safety_no_backup(game_root))


def refresh_multiplayer_safety_after_my_skin_change(root: str | Path | None, reason: str = "", bypass_liverylab_safety: bool = False) -> dict[str, Any]:
    reason_text = str(reason or "My Skin registry changed").strip()
    if not root:
        return {"attempted": False, "reason": "game root missing", "workflow": reason_text}
    if bypass_liverylab_safety or liverylab_safety_bypassed():
        return {"attempted": False, "status": "skipped", "reason": "LiveryLab Safety bypassed", "workflow": reason_text, "bypassed": True}
    try:
        game_root = normalize_game_root(root)
        result = refresh_multiplayer_safety_manifest(game_root)
        status = result.get("status") if isinstance(result.get("status"), dict) else {}
        return {
            "attempted": True,
            "status": "refreshed",
            "workflow": reason_text,
            "installed": bool(status.get("installed")),
            "unsafeSkinCount": int(status.get("unsafeSkinCount") or len(result.get("unsafeSkins") or [])),
            "manifestPath": status.get("manifestPath") or "",
            "label": status.get("label") or "",
        }
    except Exception as exc:
        return {
            "attempted": True,
            "status": "warning",
            "workflow": reason_text,
            "error": str(exc),
        }


def refresh_multiplayer_safety_after_my_skin_entry_change(
    entry: dict[str, Any] | None,
    root: str | Path | None = None,
    reason: str = "",
    bypass_liverylab_safety: bool = False,
) -> dict[str, Any]:
    reason_text = str(reason or "My Skin registry changed").strip()
    if not my_skin_uses_liverylab_safety(entry):
        return {
            "attempted": False,
            "hidden": True,
            "reason": "Official external liveries do not use LiveryLab Safety",
            "workflow": reason_text,
            "officialExternal": True,
        }
    data = entry if isinstance(entry, dict) else {}
    actual_root = root or data.get("game_root") or None
    return refresh_multiplayer_safety_after_my_skin_change(actual_root, reason_text, bypass_liverylab_safety)


def startup_multiplayer_safety_reconcile(root: str | Path | None = None) -> dict[str, Any]:
    if not root:
        return {"attempted": False, "state": "skipped", "reason": "game root missing"}
    try:
        game_root = normalize_game_root(root)
        if not game_root.exists():
            return {"attempted": False, "state": "skipped", "reason": "game root missing", "root": str(game_root)}
        unsafe_skin_items = list_my_skins(game_root, include_hidden=True)
        active_items = [item for item in unsafe_skin_items if item.get("active") is not False]
        unsafe_rows = multiplayer_safety.unsafe_skins_from_registry(unsafe_skin_items)
        active_rows = multiplayer_safety.unsafe_skins_from_registry(active_items)
        if not active_rows:
            return {
                "attempted": False,
                "state": "skipped",
                "reason": "no active My Liveries",
                "root": str(game_root),
                "activeSkinCount": 0,
                "protectedSkinCount": len(unsafe_rows),
            }

        status_before = multiplayer_safety.status(game_root, get_multiplayer_safety_gamefile_backup_root())
        expected_script = multiplayer_safety.unsafe_skins_script(unsafe_skin_items)
        paths = multiplayer_safety.paths(game_root)
        current_script = paths["unsafe_skins_js"].read_text(encoding="utf-8", errors="ignore") if paths["unsafe_skins_js"].exists() else ""
        needs_manifest_refresh = current_script != expected_script or int(status_before.get("unsafeSkinCount") or -1) != len(unsafe_rows)

        if not status_before.get("installed") or status_before.get("needsRepair"):
            result = repair_multiplayer_safety_hook(game_root)
            safety_status = result.get("status") if isinstance(result.get("status"), dict) else {}
            state = "repaired"
        elif needs_manifest_refresh:
            result = refresh_multiplayer_safety_manifest(game_root)
            safety_status = result.get("status") if isinstance(result.get("status"), dict) else {}
            state = "refreshed"
        else:
            result = {}
            safety_status = status_before
            state = "current"

        return _json_ready(
            {
                "attempted": state in {"repaired", "refreshed"},
                "state": state,
                "root": str(game_root),
                "activeSkinCount": len(active_rows),
                "protectedSkinCount": len(unsafe_rows),
                "installed": bool(safety_status.get("installed")),
                "needsRepair": bool(safety_status.get("needsRepair")),
                "label": safety_status.get("label") or "",
                "safetyStatus": safety_status,
                "result": result,
            }
        )
    except Exception as exc:
        return {
            "attempted": True,
            "state": "warning",
            "root": str(root or ""),
            "error": str(exc),
        }


def ensure_advanced_runtime_safety_ready(root: str | Path | None = None) -> dict[str, Any]:
    result = startup_multiplayer_safety_reconcile(root)
    state = str(result.get("state") or "").strip().lower()
    reason = str(result.get("reason") or "").strip()
    active_count = int(result.get("activeSkinCount") or 0)
    if state == "skipped" and active_count == 0 and reason == "no active My Liveries":
        return _json_ready({**result, "ready": True})

    installed = bool(result.get("installed"))
    needs_repair = bool(result.get("needsRepair"))
    if state in {"current", "repaired", "refreshed"} and active_count > 0 and installed and not needs_repair:
        return _json_ready({**result, "ready": True})

    detail = str(result.get("error") or reason or result.get("label") or state or "unknown Safety state").strip()
    raise RuntimeError(
        "Advanced runtime preparation could not verify LiveryLab Safety. "
        "The runtime switch cannot complete while active Advanced liveries exist without the multiplayer block. "
        f"Details: {detail}"
    )


def ensure_multiplayer_safety_ready_for_skin_write(root: str | Path | None = None, reason: str = "") -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    reason_text = str(reason or "write custom skin into AC EVO").strip()
    if liverylab_safety_bypassed():
        return {"bypassed": True, "installed": False, "label": "Bypassed", "root": str(game_root)}
    status = multiplayer_safety.status(game_root, get_multiplayer_safety_gamefile_backup_root())
    if not status.get("installed") or status.get("needsRepair"):
        repair_multiplayer_safety_hook(game_root)
        status = multiplayer_safety.status(game_root, get_multiplayer_safety_gamefile_backup_root())
    if not status.get("installed"):
        label = status.get("label") or "missing"
        if status.get("blocked"):
            label = status.get("blockReason") or label
        raise RuntimeError(
            "LiveryLab Safety must be installed before LiveryLab can write custom skin slots into AC EVO. "
            f"Current status: {label}. "
            f"Blocked workflow: {reason_text}."
        )
    return _json_ready(status)


def require_multiplayer_safety_ready(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    if liverylab_safety_bypassed():
        return {"bypassed": True, "installed": False, "label": "Bypassed", "root": str(game_root)}
    status = multiplayer_safety.status(game_root, get_multiplayer_safety_gamefile_backup_root())
    if not status.get("installed"):
        reason = status.get("label") or "missing"
        if status.get("blocked"):
            reason = status.get("blockReason") or reason
        raise RuntimeError(
            "LiveryLab Safety is required before LiveryLab can write custom skin slots into AC EVO. "
            f"Current status: {reason}."
        )
    return _json_ready(status)


def install_custom_grid_bridge(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    _require_loose_content_for_ui_hooks(game_root, "Installing the Custom Grid Bridge")
    return _json_ready(grid_bridge.install_bridge(game_root, get_custom_grid_gamefile_backup_root()))


def remove_custom_grid_bridge(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    backup_root = get_custom_grid_gamefile_backup_root()
    result = grid_bridge.remove_bridge(game_root, backup_root)
    try:
        result["aiVehicleSpecs"] = ai_vehicle_spec.cleanup_all(game_root, get_livery_library_root(), backup_root)
    except Exception as exc:
        result["aiVehicleSpecs"] = {"error": str(exc)}
    return _json_ready(result)


def export_custom_grid_bridge(root: str | Path | None, payload: dict[str, Any]) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    _require_loose_content_for_ui_hooks(game_root, "Exporting a Custom Grid to the AC EVO UI bridge")
    backup_root = get_custom_grid_gamefile_backup_root()
    bridge_status = grid_bridge.bridge_status(game_root, backup_root)
    bridge_install = None
    if (
        not bridge_status.get("installed")
        or bridge_status.get("bridgeVersionCurrent") is False
        or bridge_status.get("requiresCleanup")
    ):
        bridge_install = grid_bridge.install_bridge(game_root, backup_root)
        bridge_status = bridge_install.get("status") or grid_bridge.bridge_status(game_root, backup_root)
    prepared = ai_vehicle_spec.prepare_grid_for_export(
        game_root,
        get_livery_library_root(),
        payload or {},
        list_my_skins(game_root, include_hidden=True),
        backup_root,
    )
    result = grid_bridge.export_bridge_grid(game_root, prepared.get("grid") or payload or {}, backup_root)
    result["bridgeInstall"] = bridge_install
    result["bridgeStatus"] = bridge_status
    result["aiVehicleSpecs"] = prepared
    name_cleanup_status = grid_bridge.name_cleanup_status(game_root)
    if name_cleanup_status.get("requiresCleanup"):
        result["legacyNameCleanupRemoval"] = grid_bridge.remove_name_cleanup(game_root, backup_root)
        result["nameCleanupStatus"] = result["legacyNameCleanupRemoval"].get("status", {})
    else:
        result["nameCleanupStatus"] = name_cleanup_status
    return _json_ready(result)


def get_ai_vehicle_spec_support(root: str | Path | None = None, car_content_paths: list[Any] | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    raw_paths = car_content_paths if isinstance(car_content_paths, list) else []
    support: dict[str, Any] = {}
    for value in raw_paths:
        normalized = normalize_game_rel_path(str(value or ""))
        if not normalized:
            continue
        key = normalized.lower()
        if key in support:
            continue
        support[key] = ai_vehicle_spec.endurance_support(game_root, normalized)
    return _json_ready({"items": support})


def get_ai_name_marker_cleanup_status(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    return _json_ready(grid_bridge.name_cleanup_status(game_root))


def install_ai_name_marker_cleanup(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    result = grid_bridge.remove_name_cleanup(game_root, get_custom_grid_gamefile_backup_root())
    result["deprecated"] = True
    result["message"] = "AI name marker cleanup is no longer installed; AC EVO default name markers are kept."
    return _json_ready(result)


def remove_ai_name_marker_cleanup(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    return _json_ready(grid_bridge.remove_name_cleanup(game_root, get_custom_grid_gamefile_backup_root()))


def remove_custom_grid_ui_hooks(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    backup_root = get_custom_grid_gamefile_backup_root()
    bridge_result = grid_bridge.remove_bridge(game_root, backup_root)
    cleanup_result = grid_bridge.remove_name_cleanup(game_root, backup_root)
    try:
        ai_spec_result = ai_vehicle_spec.cleanup_all(game_root, get_livery_library_root(), backup_root)
    except Exception as exc:
        ai_spec_result = {"error": str(exc)}
    quarantine_result = quarantine_custom_grid_ui_backup_dirs(game_root)
    return _json_ready(
        {
            "bridge": bridge_result,
            "nameCleanup": cleanup_result,
            "aiVehicleSpecs": ai_spec_result,
            "quarantine": quarantine_result,
            "bridgeStatus": bridge_result.get("status", {}),
            "nameCleanupStatus": cleanup_result.get("status", {}),
        }
    )


def get_ace_grid_cache_status() -> dict[str, Any]:
    return _json_ready(ace_grid_cache.cache_status(get_livery_library_root()))


def reset_ace_grid_cache() -> dict[str, Any]:
    return _json_ready(ace_grid_cache.reset_cache(get_livery_library_root()))


def restore_ace_grid_cache_backup() -> dict[str, Any]:
    return _json_ready(ace_grid_cache.restore_latest_backup(get_livery_library_root()))


def create_driver_profile_from_skin(skin_key: str, profile_name: str | None = None, profile_id: str | None = None, overwrite: bool = False) -> dict[str, Any]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    driver_library.require_driver_car_supported(entry.get("car_model"), entry.get("car_content_path"))
    game_root = normalize_game_root(str(entry.get("game_root") or get_default_root_folder() or Path.cwd()))

    def write_driver_uv_template(category: str, mesh_ref: str, target_png: Path, width: int, height: int, overwrite_png: bool) -> dict[str, Any]:
        mesh_path = resolve_content_path(game_root, mesh_ref)
        if not mesh_path.exists():
            raise FileNotFoundError(f"Driver mesh not found: {mesh_ref}")
        mesh_uv = inspect_mesh_uv_view(mesh_path, game_root)
        return uv_templates.write_driver_uv_template(
            mesh_uv,
            target_png,
            category,
            width=width,
            height=height,
            overwrite=overwrite_png,
        )

    result = driver_library.create_driver_profile_from_skin(
        get_livery_library_root(),
        entry,
        profile_name=profile_name,
        profile_id=profile_id,
        overwrite=overwrite,
    )
    item = result.get("item") if isinstance(result.get("item"), dict) else {}
    project = driver_projects.create_project_from_profile(
        get_livery_projects_root(),
        item,
        overwrite_assets=overwrite,
        texture_to_png=lambda texture, png, overwrite_png: convert_texture_to_png(texture, png, overwrite=overwrite_png, create_dds=False),
        driver_uv_to_png=write_driver_uv_template,
    )
    result["project"] = project
    result["item"] = _attach_driver_profile_usage(driver_projects.attach_project_info(get_livery_projects_root(), item))
    return _json_ready(result)


def _driver_profile_manifest_path(profile: dict[str, Any]) -> Path:
    manifest_path_text = str(profile.get("manifestPath") or "").strip()
    manifest_path = Path(manifest_path_text).expanduser() if manifest_path_text else Path(str(profile.get("assetRoot") or "")).expanduser() / driver_library.DRIVER_MANIFEST_FILENAME
    if not manifest_path.exists() or not manifest_path.is_file():
        raise FileNotFoundError(f"Driver profile manifest not found: {manifest_path}")
    return manifest_path


def _update_driver_profile_manifest(profile: dict[str, Any], updates: dict[str, Any]) -> dict[str, Any]:
    manifest_path = _driver_profile_manifest_path(profile)
    try:
        manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    except Exception as exc:
        raise ValueError(f"Driver profile manifest could not be read: {manifest_path}") from exc
    if not isinstance(manifest, dict):
        raise ValueError(f"Driver profile manifest is invalid: {manifest_path}")
    manifest.update(_json_ready(updates))
    manifest["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    manifest_path.write_text(json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False), encoding="utf-8")
    manifest["manifestPath"] = str(manifest_path)
    manifest["assetRoot"] = str(manifest_path.parent)
    return manifest


def _refresh_driver_profile_skin_refs(
    profile: dict[str, Any],
    reason: str | None = None,
    workflow: str | None = None,
) -> dict[str, Any]:
    profile_key = str(profile.get("id") or "").strip()
    profile_car = str(profile.get("carModel") or "").strip()
    if not profile_key:
        return {"affectedSkins": [], "affectedCount": 0, "linkedSkins": [], "linkedCount": 0, "settingsPath": ""}
    skins = load_my_skins()
    affected: list[dict[str, Any]] = []
    changed = False
    mark_update = bool(reason or workflow)
    for entry in skins:
        driver = entry.get("driver_profile") if isinstance(entry.get("driver_profile"), dict) else {}
        selected_id = str(driver.get("profileId") or driver.get("id") or "").strip()
        selected_car = str(driver.get("carModel") or entry.get("car_model") or "").strip()
        if selected_id != profile_key:
            continue
        if profile_car and selected_car and sanitize_slug(profile_car) != sanitize_slug(selected_car):
            continue
        next_driver = dict(driver)
        next_driver.update(
            {
                "selection": "profile",
                "profileId": profile_key,
                "carModel": profile_car,
                "name": str(profile.get("name") or profile_key),
                "manifestPath": str(profile.get("manifestPath") or ""),
                "projectPath": str(profile.get("projectPath") or ""),
                "updatedAt": datetime.now().isoformat(timespec="seconds"),
            }
        )
        if next_driver != driver:
            entry["driver_profile"] = _json_ready(next_driver)
            changed = True
        if mark_update:
            _mark_my_skin_game_sync_required(entry, reason or "Driver profile changed", workflow or "driver_profile_details")
            changed = True
        affected.append(
            {
                "key": str(entry.get("key") or ""),
                "displayName": str(entry.get("display_name") or entry.get("skin_id") or ""),
                "skinId": str(entry.get("skin_id") or ""),
                "carModel": str(entry.get("car_model") or selected_car),
                "active": entry.get("active") is not False,
            }
        )
    settings_path = save_my_skins(skins) if changed else ""
    return {
        "affectedSkins": affected if mark_update else [],
        "affectedCount": len(affected) if mark_update else 0,
        "linkedSkins": affected,
        "linkedCount": len(affected),
        "settingsPath": settings_path,
    }


def update_driver_profile_details(
    profile_id: str,
    car_model: str | None = None,
    profile_name: str | None = None,
    source_skin_key: str | None = None,
) -> dict[str, Any]:
    profile = driver_library.get_driver_profile(get_livery_library_root(), profile_id, car_model=car_model)
    if not profile:
        raise FileNotFoundError(f"Driver profile not found: {profile_id}")
    name = str(profile_name or profile.get("name") or profile.get("id") or "").strip()
    if not name:
        raise ValueError("Driver profile name is required")
    requested_source = str(source_skin_key or "").strip()
    current_source = str(profile.get("sourceSkinKey") or "").strip()
    source_changed = bool(requested_source and requested_source != current_source)
    name_changed = name != str(profile.get("name") or "")

    if source_changed:
        source_entry, _skins, _index = _get_my_skin_entry(requested_source)
        driver_library.require_driver_car_supported(source_entry.get("car_model"), source_entry.get("car_content_path"))
        profile_car = sanitize_slug(str(profile.get("carModel") or car_model or ""))
        source_car = sanitize_slug(str(source_entry.get("car_model") or ""))
        if profile_car and source_car and profile_car != source_car:
            raise ValueError(f"Selected livery belongs to {source_entry.get('car_model') or source_car}, but this driver profile belongs to {profile.get('carModel') or profile_car}.")
        rebuild = create_driver_profile_from_skin(
            requested_source,
            name,
            profile_id=str(profile.get("id") or profile_id),
            overwrite=True,
        )
        item = rebuild.get("item") if isinstance(rebuild.get("item"), dict) else {}
        refs = _refresh_driver_profile_skin_refs(item, "Driver profile source changed", "update_driver_profile_details")
        return _json_ready(
            {
                "item": item,
                "project": rebuild.get("project") or {},
                "nameChanged": name_changed,
                "sourceChanged": True,
                "rebuilt": True,
                **refs,
            }
        )

    if name_changed:
        profile = _update_driver_profile_manifest(profile, {"name": name})
        project = driver_projects.create_project_from_profile(get_livery_projects_root(), profile, overwrite_assets=False)
    else:
        project = driver_projects.project_info_for_profile(get_livery_projects_root(), profile)
    item = _attach_driver_profile_usage(driver_projects.attach_project_info(get_livery_projects_root(), profile))
    refs = _refresh_driver_profile_skin_refs(item)
    return _json_ready(
        {
            "item": item,
            "project": project,
            "nameChanged": name_changed,
            "sourceChanged": False,
            "rebuilt": False,
            **refs,
        }
    )


def update_driver_profile_appearance(profile_id: str, car_model: str | None = None, appearance: dict[str, Any] | None = None) -> dict[str, Any]:
    profile = driver_library.get_driver_profile(get_livery_library_root(), profile_id, car_model=car_model)
    if not profile:
        raise FileNotFoundError(f"Driver profile not found: {profile_id}")
    project = driver_projects.update_project_appearance(get_livery_projects_root(), profile, appearance or {})
    skins = load_my_skins()
    profile_key = str(profile.get("id") or profile_id or "").strip()
    profile_car = str(profile.get("carModel") or car_model or "").strip()
    affected: list[dict[str, Any]] = []
    for entry in skins:
        driver = entry.get("driver_profile") if isinstance(entry.get("driver_profile"), dict) else {}
        selected_id = str(driver.get("profileId") or driver.get("id") or "").strip()
        selected_car = str(driver.get("carModel") or entry.get("car_model") or "").strip()
        if selected_id != profile_key:
            continue
        if profile_car and selected_car and sanitize_slug(profile_car) != sanitize_slug(selected_car):
            continue
        _mark_my_skin_game_sync_required(entry, "Driver appearance changed", "update_driver_profile_appearance")
        affected.append(
            {
                "key": str(entry.get("key") or ""),
                "displayName": str(entry.get("display_name") or entry.get("skin_id") or ""),
                "skinId": str(entry.get("skin_id") or ""),
                "carModel": str(entry.get("car_model") or selected_car),
                "active": entry.get("active") is not False,
            }
        )
    settings_path = save_my_skins(skins) if affected else ""
    item = _attach_driver_profile_usage(driver_projects.attach_project_info(get_livery_projects_root(), profile))
    return _json_ready(
        {
            "item": item,
            "project": project,
            "appearance": project.get("appearance") or {},
            "affectedSkins": affected,
            "affectedCount": len(affected),
            "settingsPath": settings_path,
        }
    )


def mark_driver_profile_project_changed(
    profile_id: str,
    car_model: str | None = None,
    reason: str | None = None,
    workflow: str | None = None,
) -> dict[str, Any]:
    profile = driver_library.get_driver_profile(get_livery_library_root(), profile_id, car_model=car_model)
    if not profile:
        raise FileNotFoundError(f"Driver profile not found: {profile_id}")
    profile_key = str(profile.get("id") or profile_id or "").strip()
    profile_car = str(profile.get("carModel") or car_model or "").strip()
    reason_text = str(reason or "").strip() or "Driver project PNG changed"
    workflow_text = str(workflow or "").strip() or "driver_project_png"
    skins = load_my_skins()
    affected: list[dict[str, Any]] = []
    for entry in skins:
        driver = entry.get("driver_profile") if isinstance(entry.get("driver_profile"), dict) else {}
        selected_id = str(driver.get("profileId") or driver.get("id") or "").strip()
        selected_car = str(driver.get("carModel") or entry.get("car_model") or "").strip()
        if selected_id != profile_key:
            continue
        if profile_car and selected_car and sanitize_slug(profile_car) != sanitize_slug(selected_car):
            continue
        _mark_my_skin_game_sync_required(entry, reason_text, workflow_text)
        affected.append(
            {
                "key": str(entry.get("key") or ""),
                "displayName": str(entry.get("display_name") or entry.get("skin_id") or ""),
                "skinId": str(entry.get("skin_id") or ""),
                "carModel": str(entry.get("car_model") or selected_car),
                "active": entry.get("active") is not False,
            }
        )
    settings_path = save_my_skins(skins) if affected else ""
    item = _attach_driver_profile_usage(driver_projects.attach_project_info(get_livery_projects_root(), profile))
    return _json_ready(
        {
            "item": item,
            "affectedSkins": affected,
            "affectedCount": len(affected),
            "settingsPath": settings_path,
            "reason": reason_text,
            "workflow": workflow_text,
        }
    )


def save_driver_profile_preview_image(profile_id: str, car_model: str | None = None, preview_png: str | Path | None = None) -> dict[str, Any]:
    profile = driver_library.get_driver_profile(get_livery_library_root(), profile_id, car_model=car_model)
    if not profile:
        raise FileNotFoundError(f"Driver profile not found: {profile_id}")
    if not preview_png:
        raise ValueError("Driver preview PNG is required")
    project = driver_projects.update_project_preview_image(get_livery_projects_root(), profile, preview_png)
    item = _attach_driver_profile_usage(driver_projects.attach_project_info(get_livery_projects_root(), profile))
    return _json_ready(
        {
            "item": item,
            "project": project,
            "previewPng": project.get("previewPng") or "",
            "previewPngPath": project.get("previewPngPath") or "",
        }
    )


def prepare_driver_preview_project(
    profile_id: str,
    car_model: str | None = None,
    root: str | Path | None = None,
    force: bool = False,
) -> dict[str, Any]:
    profile = driver_library.get_driver_profile(get_livery_library_root(), profile_id, car_model=car_model)
    if not profile:
        raise FileNotFoundError(f"Driver profile not found: {profile_id}")
    project = driver_projects.read_project(
        driver_projects.project_path_for(get_livery_projects_root(), str(profile.get("carModel") or car_model or ""), profile_id)
    )
    if not project:
        raise FileNotFoundError(f"Driver project not found for profile: {profile_id}")
    if root:
        game_root = normalize_game_root(root)
    elif (Path.cwd() / "content").exists():
        game_root = normalize_game_root(Path.cwd())
    elif (Path.cwd().parent / "content").exists():
        game_root = normalize_game_root(Path.cwd().parent)
    else:
        game_root = normalize_game_root(Path.cwd())
    result = driver_preview.prepare_driver_preview_project(
        str(project.get("projectPath") or ""),
        game_root,
        inspect_mesh_readable,
        force=force,
    )
    return _json_ready(result)


def _driver_profile_usage(profile: dict[str, Any] | None) -> list[dict[str, Any]]:
    if not profile:
        return []
    profile_id = str(profile.get("id") or "").strip()
    profile_car = str(profile.get("carModel") or "").strip()
    if not profile_id:
        return []
    used_by: list[dict[str, Any]] = []
    for skin in load_my_skins():
        driver = skin.get("driver_profile") if isinstance(skin.get("driver_profile"), dict) else {}
        selected_id = str(driver.get("profileId") or driver.get("id") or "").strip()
        selected_car = str(driver.get("carModel") or skin.get("car_model") or "").strip()
        if selected_id != profile_id:
            continue
        if profile_car and selected_car and sanitize_slug(profile_car) != sanitize_slug(selected_car):
            continue
        used_by.append(
            {
                "key": str(skin.get("key") or ""),
                "displayName": str(skin.get("display_name") or skin.get("skin_id") or ""),
                "skinId": str(skin.get("skin_id") or ""),
                "carModel": str(skin.get("car_model") or selected_car),
                "active": skin.get("active") is not False,
                "needsGameUpdate": bool((skin.get("game_sync") if isinstance(skin.get("game_sync"), dict) else {}).get("needs_update")),
            }
        )
    return used_by


def _attach_driver_profile_usage(profile: dict[str, Any]) -> dict[str, Any]:
    item = dict(profile)
    used_by = _driver_profile_usage(item)
    item["usedBySkins"] = used_by
    item["usedByCount"] = len(used_by)
    item["inUse"] = bool(used_by)
    return item


def delete_driver_profile(profile_id: str, car_model: str | None = None, force: bool = False) -> dict[str, Any]:
    profile = driver_library.get_driver_profile(get_livery_library_root(), profile_id, car_model=car_model)
    project = driver_projects.project_info_for_profile(get_livery_projects_root(), profile) if profile else {}
    usage = _driver_profile_usage(profile)
    if usage and not force:
        names = ", ".join(str(row.get("displayName") or row.get("skinId") or row.get("key") or "-") for row in usage[:5])
        more = f" and {len(usage) - 5} more" if len(usage) > 5 else ""
        raise RuntimeError(f"Driver profile is used by {len(usage)} My Skin(s): {names}{more}. Clear or switch those skins before deleting it.")
    result = driver_library.delete_driver_profile(get_livery_library_root(), profile_id, car_model=car_model)
    project_cleanup = driver_projects.cleanup_project_generated_files(get_livery_projects_root(), profile)
    result["project"] = project
    result["projectCleanup"] = project_cleanup
    result["usedBySkins"] = usage
    return _json_ready(result)


def update_my_skin_driver_profile(skin_key: str, profile_id: str | None = None, car_model: str | None = None) -> dict[str, Any]:
    entry, skins, index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canEditDriver")
    requested_profile = str(profile_id or "").strip()
    requested_car = str(car_model or entry.get("car_model") or "").strip()
    previous = entry.get("driver_profile") if isinstance(entry.get("driver_profile"), dict) else {}
    previous_profile = str(previous.get("profileId") or previous.get("id") or "").strip()
    previous_car = str(previous.get("carModel") or "").strip()
    actions: list[str] = []

    if not requested_profile:
        if previous:
            entry.pop("driver_profile", None)
            _mark_my_skin_game_sync_required(entry, "Driver profile changed", "update_my_skin_driver_profile")
            actions.append("driver profile reset to skin/default driver")
            changed = True
        else:
            actions.append("driver profile already set to skin/default driver")
            changed = False
    else:
        driver_library.require_driver_car_supported(requested_car, entry.get("car_content_path"))
        profile = driver_library.get_driver_profile(get_livery_library_root(), requested_profile, requested_car or None)
        if not profile:
            raise KeyError(f"Driver profile not found: {requested_profile}")
        profile = driver_projects.attach_project_info(get_livery_projects_root(), profile)
        profile_car = str(profile.get("carModel") or requested_car or "").strip()
        skin_car = str(entry.get("car_model") or "").strip()
        if skin_car and profile_car and sanitize_slug(profile_car) != sanitize_slug(skin_car):
            raise ValueError(f"Driver profile belongs to {profile_car}, but skin belongs to {skin_car}")
        next_driver = {
            "selection": "profile",
            "profileId": str(profile.get("id") or requested_profile),
            "carModel": profile_car,
            "name": str(profile.get("name") or profile.get("id") or requested_profile),
            "manifestPath": str(profile.get("manifestPath") or ""),
            "projectPath": str(profile.get("projectPath") or ""),
            "updatedAt": datetime.now().isoformat(timespec="seconds"),
        }
        compare_keys = ("selection", "profileId", "carModel", "name", "manifestPath", "projectPath")
        changed = any(str(previous.get(key) or "") != str(next_driver.get(key) or "") for key in compare_keys)
        entry["driver_profile"] = _json_ready(next_driver)
        if changed:
            _mark_my_skin_game_sync_required(entry, "Driver profile changed", "update_my_skin_driver_profile")
            actions.append(f"driver profile set to {next_driver['name']}")
        else:
            actions.append("driver profile already current")

    skins[index] = entry
    saved = save_my_skins(skins)
    return {
        "item": entry,
        "changed": changed,
        "actions": actions,
        "settingsPath": saved,
    }


def update_my_skin_numberplate(
    skin_key: str,
    font_path: str | None = None,
    background_path: str | None = None,
    mode: str | None = None,
    hood_background_path: str | None = None,
    left_background_path: str | None = None,
    right_background_path: str | None = None,
    text_color: list[Any] | tuple[Any, ...] | None = None,
    template_id: str | None = None,
    start_number: str | None = None,
    source_type: str | None = None,
) -> dict[str, Any]:
    entry, skins, index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canEditNumberplate")
    previous_variant = entry.get("numberplate_variant") if isinstance(entry.get("numberplate_variant"), dict) else {}
    game_root = normalize_game_root(str(entry.get("game_root", "")))
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    actor_rel = str(files.get("actor") or "")
    visual_rel = str(files.get("visual_preset") or "")
    source_actor_path = _skin_file_disk_path(game_root, entry, actor_rel)
    source_visual_path = _skin_file_disk_path(game_root, entry, visual_rel)
    if source_actor_path is None or not source_actor_path.exists():
        raise FileNotFoundError("Actor file for this skin was not found")
    if source_visual_path is None or not source_visual_path.exists():
        raise FileNotFoundError("Visual preset for this skin was not found")
    actor_path = _ensure_local_skin_file_copy(game_root, entry, actor_rel, source=source_actor_path)
    visual_path = _ensure_local_skin_file_copy(game_root, entry, visual_rel, source=source_visual_path)
    if actor_path is None or not actor_path.exists():
        raise FileNotFoundError("Local actor file for this skin could not be prepared")
    if visual_path is None or not visual_path.exists():
        raise FileNotFoundError("Local visual preset for this skin could not be prepared")
    local_root = _entry_local_root(entry)
    writer_root = local_root or actor_path.parent
    if template_id and not str(start_number or "").strip():
        raise ValueError("A start number is required for LiveryLab numberplate templates.")
    local_carfinalstate_paths = _entry_local_carfinalstate_paths(entry)
    writer = GameFileWriter(
        writer_root,
        "update My Skin numberplate",
        [actor_path, visual_path, *local_carfinalstate_paths],
    )
    normalized_mode = str(mode or "").strip().lower()
    normalized_source = str(source_type or "").strip().lower()
    if normalized_source in {"road_custom", "road-custom", "custom_road_plate", "custom-road-plate", "road_default", "road-default", "default_road_plate", "default-road-plate"}:
        support_state = _ensure_road_plate_metadata_for_entry(entry, game_root)
        support = support_state.get("support") if isinstance(support_state.get("support"), dict) else {}
        if not support.get("supported"):
            raise RuntimeError(f"This car does not support a rear road plate: {support.get('reason') or 'not supported'}")
        road_plate = dict(entry.get("road_plate") or {}) if isinstance(entry.get("road_plate"), dict) else {}
        previous_source = str(road_plate.get("source") or "default").strip().lower()
        actions: list[str] = []
        changed = bool(support_state.get("changed"))
        result: dict[str, Any] = {"support": _json_ready(support)}
        if normalized_source in {"road_custom", "road-custom", "custom_road_plate", "custom-road-plate"}:
            project_result = _ensure_road_plate_project_asset_for_entry(entry, game_root)
            road_plate = dict(entry.get("road_plate") or {}) if isinstance(entry.get("road_plate"), dict) else {}
            project_png = _road_plate_project_png_path_for_entry(entry)
            road_plate["supported"] = True
            road_plate["kind"] = "road_rear"
            road_plate["source"] = "custom"
            road_plate["projectPng"] = road_plates.PROJECT_PLATE_PNG
            current_hash = _file_sha256(project_png)
            if previous_source != "custom":
                changed = True
            entry["road_plate"] = _json_ready(road_plate)
            if bool(entry.get("active")):
                _mark_my_skin_game_sync_required(entry, "Custom rear plate changed", "road_plate")
            actions.append(
                "custom rear plate PNG "
                + ("created in LiveryProjects" if project_result.get("status") == "created" else "already available in LiveryProjects")
            )
            actions.append("custom rear plate will be applied with Update in Game")
            result.update({"project": _json_ready(project_result), "projectPng": str(project_png), "projectPngHash": current_hash})
        else:
            for key in ("lastSyncedProjectPngHash",):
                road_plate.pop(key, None)
            road_plate["supported"] = True
            road_plate["kind"] = "road_rear"
            road_plate["source"] = "default"
            entry["road_plate"] = _json_ready(road_plate)
            if previous_source == "custom":
                changed = True
                if bool(entry.get("active")):
                    _mark_my_skin_game_sync_required(entry, "Rear plate restored to AC EVO default", "road_plate")
            actions.append("rear plate set to AC EVO default")
            actions.append("project plate_r.png was kept in the LiveryProjects folder")
        skins[index] = entry
        saved = save_my_skins(skins)
        return {
            "item": entry,
            "changed": changed,
            "actions": actions,
            "result": result,
            "settingsPath": saved,
            "writer": writer.to_dict(),
        }
    coerced_text_color = _coerce_actor_numberplate_text_color(text_color)
    if not NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED:
        requested_custom_source = normalized_source not in NUMBERPLATE_NONE_SOURCE_TYPES | NUMBERPLATE_STOCK_SOURCE_TYPES
        requested_custom_assets = any(str(value or "").strip() for value in (
            font_path,
            background_path,
            hood_background_path,
            left_background_path,
            right_background_path,
            template_id,
        ))
        if requested_custom_source or requested_custom_assets or coerced_text_color is not None:
            raise RuntimeError(NUMBERPLATE_GAME_EXPORT_DISABLED_MESSAGE)
        if normalized_mode in NUMBERPLATE_NONE_SOURCE_TYPES:
            normalized_source = "none"
        elif normalized_source in NUMBERPLATE_NONE_SOURCE_TYPES:
            normalized_source = "none"
        else:
            normalized_source = "acevo"
            normalized_mode = ""
    baked_numberplate = None
    is_none_source = normalized_source in NUMBERPLATE_NONE_SOURCE_TYPES
    is_stock_source = normalized_source in NUMBERPLATE_STOCK_SOURCE_TYPES
    normalized_start_number = _sanitize_start_number(start_number)
    if is_none_source and not normalized_start_number:
        raise ValueError("A start number is required when No numberplate is selected.")
    previous_source = str(previous_variant.get("source_type") or previous_variant.get("numberplate_source") or "").strip().lower()
    previous_is_template_variant = (
        previous_variant.get("baked_template") is True
        or bool(str(previous_variant.get("template_id") or "").strip())
        or previous_source == "template"
    )
    cleanup_result: dict[str, Any] = {}
    should_cleanup_owned_variant = (
        not str(template_id or "").strip()
        and (
            (is_none_source and previous_variant.get("owns_actor_numberplate") is True)
            or (is_stock_source and (previous_variant.get("owns_actor_numberplate") is True or previous_is_template_variant))
        )
    )
    if should_cleanup_owned_variant:
        cleanup_result = _cleanup_owned_numberplate_variant_for_source_reset(
            game_root,
            entry,
            actor_path,
            visual_path,
            previous_variant,
            writer=writer,
        )
    if is_none_source:
        result = apply_visual_numberplate_hidden_variant(
            actor_path,
            visual_path,
            previous_variant=previous_variant,
            writer=writer,
        )
        start_number_changed, start_number_backup = update_visualpreset_file(
            visual_path,
            start_number=normalized_start_number,
            writer=writer,
        )
        carfinalstate_sync = _sync_local_carfinalstate_numberplate_selectors(
            entry,
            visual_path,
            writer,
        )
        variant = result.get("variant") if isinstance(result.get("variant"), dict) else {}
        variant["source_type"] = "none"
        variant["numberplate_source"] = "none"
        variant["numberplate_hidden"] = True
        variant["start_number"] = normalized_start_number
        entry["start_number"] = normalized_start_number
        entry.pop("numberplate_bake", None)
        entry.pop("numberplate_generated_font", None)
        entry["numberplate_variant"] = _json_ready(variant)
        changed = bool(
            result.get("visual_changed")
            or start_number_changed
            or carfinalstate_sync.get("changed")
            or cleanup_result.get("changed")
        )
        if changed:
            _mark_my_skin_game_sync_required(entry, "Numberplate hidden", "update_my_skin_numberplate")
        skins[index] = entry
        saved = save_my_skins(skins)
        actions = _numberplate_cleanup_actions(cleanup_result)
        actions.append(
            "local visual AC EVO no-numberplate selector enabled"
            + (f" (backup: {result.get('visual_backup') or '-'})" if result.get("visual_changed") else "")
        )
        actions.append(
            f"local car final state no-numberplate selectors "
            f"{'updated (' + str(carfinalstate_sync.get('changedCount') or 0) + ')' if carfinalstate_sync.get('changed') else 'already current'}"
        )
        actions.append(
            f"local visual start number {'set to ' + normalized_start_number if start_number_changed else 'already ' + normalized_start_number}"
            + (f" (backup: {start_number_backup or '-'})" if start_number_changed else "")
        )
        return {
            "item": entry,
            "changed": changed,
            "actions": actions,
            "result": {
                **result,
                "cleanup": _json_ready(cleanup_result) if cleanup_result else {},
                "carFinalStateSync": _json_ready(carfinalstate_sync),
                "startNumberChanged": start_number_changed,
                "startNumberBackup": str(start_number_backup or ""),
            },
            "settingsPath": saved,
            "writer": writer.to_dict(),
        }
    restoring_stock_numberplate = (
        is_stock_source
        and not str(template_id or "").strip()
        and not any(str(value or "").strip() for value in (font_path, background_path, hood_background_path, left_background_path, right_background_path))
        and coerced_text_color is None
    )
    if restoring_stock_numberplate:
        source_selector, selector_source = _source_numberplate_selector_for_entry(game_root, entry)
        visual_changed, visual_backup = replace_visual_numberplate_selector_file(
            visual_path,
            source_selector,
            writer=writer,
        )
        carfinalstate_sync = _sync_local_carfinalstate_numberplate_selectors(
            entry,
            visual_path,
            writer,
        )
        variant = inspect_visual_numberplate_variant(actor_path, visual_path)
        variant["source_type"] = "acevo"
        variant["numberplate_source"] = "acevo"
        variant["numberplate_hidden"] = False
        variant["owns_actor_numberplate"] = False
        entry.pop("numberplate_bake", None)
        entry.pop("numberplate_generated_font", None)
        entry["numberplate_variant"] = _json_ready(variant)
        changed = bool(
            cleanup_result.get("changed")
            or visual_changed
            or carfinalstate_sync.get("changed")
        )
        if changed:
            _mark_my_skin_game_sync_required(entry, "Numberplate changes", "update_my_skin_numberplate")
        skins[index] = entry
        saved = save_my_skins(skins)
        actions = _numberplate_cleanup_actions(cleanup_result)
        actions.append(
            "local visual numberplate selector restored from AC EVO source"
            + (f" ({selector_source or 'empty selector'}, backup: {visual_backup or '-'})" if visual_changed else "")
        )
        actions.append(
            f"local car final state numberplate selectors "
            f"{'updated (' + str(carfinalstate_sync.get('changedCount') or 0) + ')' if carfinalstate_sync.get('changed') else 'already current'}"
        )
        return {
            "item": entry,
            "changed": changed,
            "actions": actions,
            "result": {
                "cleanup": _json_ready(cleanup_result),
                "variant": _json_ready(variant),
                "selectorSource": selector_source,
                "carFinalStateSync": _json_ready(carfinalstate_sync),
            },
            "settingsPath": saved,
            "writer": writer.to_dict(),
        }
    if template_id and str(start_number or "").strip():
        bake_surface_keys = ["hood", "leftDoor", "rightDoor"]
        baked_numberplate = _bake_my_skin_numberplate_template(entry, template_id, start_number, bake_surface_keys)
        baked_surfaces = baked_numberplate.get("surfaces", {}) if isinstance(baked_numberplate, dict) else {}
        hood_background_path = str((baked_surfaces.get("hood") or {}).get("textureRel") or hood_background_path or "")
        left_background_path = str((baked_surfaces.get("leftDoor") or {}).get("textureRel") or left_background_path or "")
        right_background_path = str((baked_surfaces.get("rightDoor") or {}).get("textureRel") or right_background_path or "")
        font_path = _numberplate_template_font_base_path(template_id) or font_path
        coerced_text_color = [1.0, 1.0, 1.0, 0.0]
    published_font_path = "" if baked_numberplate else _numberplate_template_published_font_path(template_id)
    if published_font_path:
        font_path = published_font_path
    if not template_id and coerced_text_color is None and not cleanup_result.get("attempted") and _numberplate_variant_should_restore_visible_text(previous_variant):
        coerced_text_color = _visible_actor_numberplate_text_color(actor_path.read_bytes(), visual_path.read_bytes())
    pre_visibility_restore: dict[str, Any] = {}
    if (
        not is_none_source
        and (
            previous_variant.get("numberplate_hidden") is True
            or previous_source == "none"
        )
    ):
        pre_visibility_restore = apply_visual_numberplate_visibility(actor_path, visual_path, visible=True, writer=writer)
    text_fields_by_surface = _numberplate_template_text_fields_by_surface(
        template_id,
        actor_path.read_bytes(),
        hood_background_path=hood_background_path,
        left_background_path=left_background_path,
        right_background_path=right_background_path,
    ) if template_id and actor_path.exists() else {}
    if normalized_mode in {"surface", "surfaces", "per_surface", "per-surface", "hood", "hood_split", "hood-left-right", "hood_left_right"}:
        result = apply_visual_numberplate_surface_variant(
            actor_path,
            visual_path,
            font_path=font_path,
            hood_background_path=hood_background_path,
            left_background_path=left_background_path,
            right_background_path=right_background_path,
            text_color=coerced_text_color,
            text_fields_by_surface=text_fields_by_surface,
            writer=writer,
        )
    elif normalized_mode in {"split", "separate", "left_right", "left-right"}:
        result = apply_visual_numberplate_pair_variant(
            actor_path,
            visual_path,
            font_path=font_path,
            left_background_path=left_background_path,
            right_background_path=right_background_path,
            text_color=coerced_text_color,
            writer=writer,
        )
    else:
        result = apply_visual_numberplate_variant(
            actor_path,
            visual_path,
            font_path=font_path,
            background_path=background_path,
            text_color=coerced_text_color,
            writer=writer,
        )
    variant = result.get("variant") if isinstance(result.get("variant"), dict) else {}
    if previous_variant.get("owns_actor_numberplate") is True and not cleanup_result.get("attempted"):
        try:
            previous_index = int(previous_variant.get("numberplate_index"))
            current_index = int(variant.get("numberplate_index"))
        except Exception:
            previous_index = -1
            current_index = -2
        previous_indices = sorted(int(value) for value in previous_variant.get("numberplate_indices", []) or [] if str(value).strip())
        current_indices = sorted(int(value) for value in variant.get("numberplate_indices", []) or [] if str(value).strip())
        if previous_index == current_index or (previous_indices and previous_indices == current_indices):
            variant["created_by"] = previous_variant.get("created_by") or "LiveryLab"
            variant["owns_actor_numberplate"] = True
            if isinstance(previous_variant.get("placement_sources"), dict) and not isinstance(variant.get("placement_sources"), dict):
                variant["placement_sources"] = _json_ready(previous_variant.get("placement_sources"))
        previous_sources = previous_variant.get("placement_sources") if isinstance(previous_variant.get("placement_sources"), dict) else {}
        current_sources = variant.get("placement_sources") if isinstance(variant.get("placement_sources"), dict) else {}
        if previous_sources and previous_indices and set(previous_indices).issubset(set(current_indices)):
            merged_sources = dict(previous_sources)
            merged_sources.update(current_sources)
            variant["placement_sources"] = _json_ready(merged_sources)
    if template_id:
        variant["template_id"] = str(template_id)
        variant["source_type"] = "template"
        variant["numberplate_source"] = "template"
    else:
        variant.pop("template_id", None)
        variant["source_type"] = "acevo"
        variant["numberplate_source"] = "acevo"
    if published_font_path:
        variant["published_template_font_path"] = published_font_path
    if text_fields_by_surface:
        variant["game_text_fields_by_surface"] = _json_ready(text_fields_by_surface)
    if baked_numberplate:
        variant["baked_template"] = True
        variant["start_number"] = baked_numberplate.get("start_number")
        entry["numberplate_bake"] = _json_ready(baked_numberplate)
        entry["start_number"] = str(baked_numberplate.get("start_number") or "")
    else:
        entry.pop("numberplate_bake", None)
    entry.pop("numberplate_generated_font", None)
    entry["numberplate_variant"] = _json_ready(variant)
    if result.get("actor_changed") or result.get("visual_changed") or baked_numberplate or pre_visibility_restore.get("actor_changed") or cleanup_result.get("changed"):
        _mark_my_skin_game_sync_required(entry, "Numberplate changes", "update_my_skin_numberplate")
    skins[index] = entry
    saved = save_my_skins(skins)
    actions = _numberplate_cleanup_actions(cleanup_result)
    if pre_visibility_restore.get("actor_changed"):
        actions.append(f"local actor numberplate visibility restored (backup: {pre_visibility_restore.get('actor_backup') or '-'})")
    if result.get("actor_changed"):
        actions.append(f"local actor numberplate variant updated (backup: {result.get('actor_backup') or '-'})")
    else:
        actions.append("local actor numberplate variant already current")
    if result.get("visual_changed"):
        actions.append(f"local visual numberplate selector updated (backup: {result.get('visual_backup') or '-'})")
    if published_font_path:
        actions.append(f"numberplate template font: {published_font_path}")
    if baked_numberplate:
        baked_surfaces = baked_numberplate.get("surfaces") if isinstance(baked_numberplate.get("surfaces"), dict) else {}
        actions.append(f"baked numberplates prepared locally ({len(baked_surfaces)} surfaces)")
    return {
        "item": entry,
        "changed": bool(result.get("actor_changed") or result.get("visual_changed") or pre_visibility_restore.get("actor_changed") or cleanup_result.get("changed")),
        "actions": actions,
        "result": {**result, "cleanup": _json_ready(cleanup_result) if cleanup_result else {}},
        "settingsPath": saved,
        "writer": writer.to_dict(),
    }


def _inactive_migrated_advanced_local_trash_eligibility(entry: dict[str, Any]) -> dict[str, Any]:
    return migrated_livery_trash.eligibility(
        entry,
        storage_root=get_portable_storage_root(),
        local_skins_root=get_local_skins_root(),
        projects_root=get_livery_projects_root(),
    )


def _trash_inactive_migrated_advanced_copy(
    entry: dict[str, Any],
    skins: list[dict[str, Any]],
    index: int,
    *,
    include_livery_project: bool,
) -> dict[str, Any]:
    eligibility = _inactive_migrated_advanced_local_trash_eligibility(entry)
    if eligibility.get("eligible") is not True:
        raise PermissionError(
            "Local migrated-livery trash is not allowed: "
            + "; ".join(str(reason) for reason in eligibility.get("reasons") or ["eligibility check failed"])
        )

    local_root = Path(str(eligibility.get("localRoot") or "")).resolve()
    project_roots = [Path(str(path)).resolve() for path in eligibility.get("projectRoots") or [] if str(path).strip()]
    project_folder = project_roots[0] if project_roots else _livery_project_folder_for_entry(entry).resolve()
    trash_dir = _make_trash_session_dir(get_portable_storage_root(), entry)
    local_relative = _local_skin_storage_trash_relative(local_root)
    local_target = trash_dir / local_relative
    moves: list[tuple[Path, Path]] = [(local_root, local_target)]
    project_target: Path | None = None
    if include_livery_project and project_folder.exists() and project_folder.is_dir():
        if not _path_is_within(project_folder, get_livery_projects_root()):
            shutil.rmtree(trash_dir, ignore_errors=True)
            raise PermissionError(f"Migrated project folder is outside LiveryProjects: {project_folder}")
        project_relative = project_folder.resolve().relative_to(get_livery_projects_root().resolve())
        project_target = trash_dir / LIVERY_PROJECTS_DIRNAME / project_relative
        moves.append((project_folder, project_target))

    completed_moves: list[dict[str, str]] = []
    moved = [norm_slashes(str(local_relative))]
    if project_target is not None:
        moved.append(norm_slashes(str(project_target.relative_to(trash_dir))))
    deactivation = {
        "changed": False,
        "skipped": "inactive migrated Advanced copy; no game deactivation required",
    }
    savedcars_quarantine = {
        "moved": [],
        "errors": [],
        "skipped": "inactive migrated Advanced copy; SavedCars were not touched",
    }
    livery_project = {
        "requested": bool(include_livery_project),
        "moved": project_target is not None,
        "path": str(project_folder),
        "relative": norm_slashes(str(project_target.relative_to(trash_dir))) if project_target is not None else "",
        "warning": (
            "LiveryProject folder not found"
            if include_livery_project and not project_folder.exists()
            else ("LiveryProject was kept by request" if not include_livery_project and project_folder.exists() else "")
        ),
        "protectedPaths": [],
        "managedMoved": [],
        "remainingFileCount": _livery_project_remaining_file_count(project_folder) if project_target is None else 0,
    }
    manifest = {
        "action": "trash_my_skin",
        "trashedAt": datetime.now().isoformat(timespec="seconds"),
        "skin": entry,
        "localOnlyMigrationTrash": True,
        "deactivation": deactivation,
        "road_plate_cleanup": {},
        "moved": moved,
        "missing": [],
        "savedcars_quarantine": savedcars_quarantine,
        "livery_project": livery_project,
        "loc_changes": [],
        "actor_cleanup": {
            "changed": False,
            "skipped": "inactive migrated Advanced copy; game actor was not touched",
        },
    }

    try:
        completed_moves = migrated_livery_trash.move_transaction(moves)
        (trash_dir / "trash_manifest.json").write_text(
            json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False),
            encoding="utf-8",
        )
        del skins[index]
        saved = save_my_skins(skins)
    except Exception:
        if completed_moves:
            rollback_moves = [
                (Path(row["target"]), Path(row["source"]))
                for row in reversed(completed_moves)
            ]
            try:
                migrated_livery_trash.move_transaction(rollback_moves)
            except Exception as rollback_error:
                raise RuntimeError(
                    "Local migrated-livery trash failed and rollback was incomplete: "
                    f"{rollback_error}"
                ) from rollback_error
        shutil.rmtree(trash_dir, ignore_errors=True)
        raise

    return {
        "item": entry,
        "trashDir": trash_dir,
        "moved": moved,
        "missing": [],
        "savedCarsQuarantine": savedcars_quarantine,
        "liveryProject": livery_project,
        "deactivation": deactivation,
        "roadPlateCleanup": {},
        "locChanges": [],
        "actorCleanup": manifest["actor_cleanup"],
        "settingsPath": saved,
        "safetyRefresh": {
            "attempted": False,
            "status": "skipped",
            "reason": "inactive migrated Advanced copy; game files were not touched",
            "workflow": "trash_my_skin",
        },
        "manifestWarning": "",
        "writer": {
            "localOnly": True,
            "operation": "trash inactive migrated Advanced copy",
            "moves": completed_moves,
        },
        "localOnlyMigrationTrash": True,
    }


def trash_my_skin(skin_key: str, include_livery_project: bool = False, bypass_liverylab_safety: bool = False) -> dict[str, Any]:
    entry, skins, index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canTrash")
    local_migration_eligibility = _inactive_migrated_advanced_local_trash_eligibility(entry)
    if local_migration_eligibility.get("eligible") is True:
        return _trash_inactive_migrated_advanced_copy(
            entry,
            skins,
            index,
            include_livery_project=include_livery_project,
        )
    game_root = _resolve_my_skin_game_root(entry)
    if not game_root.exists():
        raise FileNotFoundError(f"Game root not found: {game_root}")

    _normalize_my_skin_file_refs(entry)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_id = str(entry.get("skin_id") or "").strip()
    display_name = str(entry.get("display_name") or skin_id or skin_key).strip()
    visual_loc_key = str(entry.get("visual_loc_key") or "").strip()
    actor_path = _disk_from_game_rel(game_root, str(files.get("actor") or ""))
    mechanical_path = _disk_from_game_rel(game_root, str(files.get("mechanical_preset") or ""))

    loc_changes: list[dict[str, Any]] = []
    loc_candidates: list[Path] = []
    if visual_loc_key:
        raw_loc_files = entry.get("loc_files", [])
        if isinstance(raw_loc_files, list):
            for rel in raw_loc_files:
                disk = _disk_from_game_rel(game_root, str(rel or ""))
                if disk and disk.exists():
                    loc_candidates.append(disk)
        if not loc_candidates:
            loc_candidates = iter_car_loc_files(game_root)

    move_candidates: list[Path] = []
    protected_project_paths: list[str] = []
    movable_kinds = {"cardesign", "design", "skin_folder", "material", "visual_preset", "thumbnail_png"}
    for _kind, rel in _file_rel_values(files, movable_kinds):
        disk = _disk_from_game_rel(game_root, rel)
        if disk and disk.exists():
            move_candidates.append(disk.resolve())
    local_root = _entry_local_root(entry)
    if local_root and local_root.exists():
        resolved_local_root = local_root.resolve()
        if _is_livery_project_path(resolved_local_root):
            protected_project_paths.append(str(resolved_local_root))
        elif _is_local_skin_storage_path(resolved_local_root):
            move_candidates.append(resolved_local_root)
    filtered_move_candidates: list[Path] = []
    for path in move_candidates:
        resolved = path.resolve()
        if _is_livery_project_path(resolved):
            protected_project_paths.append(str(resolved))
            continue
        filtered_move_candidates.append(resolved)
    move_candidates = _remove_child_paths(filtered_move_candidates)

    visual_refs = _my_skin_ref_candidates(entry, game_root, str(files.get("visual_preset") or ""))
    cleanup_plan = _my_skin_deactivation_cleanup_plan(entry, game_root, actor_path, visual_refs)
    writer_targets = [
        path
        for path in (
            actor_path,
            mechanical_path,
            *(cleanup_plan.get("mechanical_paths") or []),
            *(cleanup_plan.get("matrix_paths") or []),
            *loc_candidates,
            *(path for path in move_candidates if _path_is_within(path, game_root)),
        )
        if path
    ]
    writer = GameFileWriter(game_root, "trash My Skin", writer_targets)

    deactivation = set_my_skin_active(skin_key, False, writer=writer, delete_savedcars_on_deactivate=False)
    road_plate_cleanup: dict[str, Any] = {}
    if isinstance(entry.get("road_plate"), dict):
        try:
            road_plate_cleanup = _cleanup_road_plate_for_entry(
                dict(entry),
                game_root,
                visual_refs[0] if visual_refs else str(files.get("visual_preset") or ""),
                writer,
                cleanup_plan.get("mechanical_rels") or [],
            )
        except Exception as exc:
            road_plate_cleanup = {"changed": False, "error": str(exc)}

    if visual_loc_key:
        seen_loc: set[str] = set()
        for loc_path in loc_candidates:
            key = str(loc_path).lower()
            if key in seen_loc:
                continue
            seen_loc.add(key)
            changed, backup = remove_loc_entry(loc_path, visual_loc_key, writer=writer)
            loc_changes.append(
                {
                    "path": loc_path,
                    "changed": changed,
                    "backup": backup,
                }
            )

    trash_dir = _make_trash_session_dir(game_root, entry)
    savedcars_quarantine = quarantine_my_skin_savedcars(entry, trash_dir)
    moved: list[str] = []
    missing: list[str] = []
    livery_project: dict[str, Any] = {
        "requested": bool(include_livery_project),
        "moved": False,
        "path": "",
        "relative": "",
        "warning": "",
        "protectedPaths": protected_project_paths,
        "managedMoved": [],
        "remainingFileCount": 0,
    }

    for path in move_candidates:
        if not path.exists():
            try:
                if _is_local_skin_storage_path(path):
                    missing.append(norm_slashes(str(_local_skin_storage_trash_relative(path))))
                else:
                    missing.append(norm_slashes(str(path.resolve().relative_to(game_root.resolve()))))
            except Exception:
                missing.append(str(path))
            continue
        if _is_local_skin_storage_path(path):
            moved.append(_move_local_skin_storage_to_trash(path, trash_dir))
        else:
            moved.append(_move_path_to_trash(path, game_root, trash_dir, writer=writer))

    project_folder = _livery_project_folder_for_entry(entry)
    livery_project["path"] = str(project_folder)
    if include_livery_project:
        if project_folder.exists() and project_folder.is_dir():
            try:
                moved_rel = _move_livery_project_to_trash(project_folder, trash_dir, writer=writer)
                livery_project.update({"moved": True, "relative": moved_rel})
            except Exception as exc:
                livery_project["warning"] = str(exc)
        else:
            livery_project["warning"] = "LiveryProject folder not found"
    elif project_folder.exists() and project_folder.is_dir():
        managed_moved: list[str] = []
        for path in _livery_project_managed_trash_candidates(project_folder):
            if not path.exists():
                continue
            try:
                managed_moved.append(_move_livery_project_to_trash(path, trash_dir, writer=writer))
            except Exception as exc:
                livery_project["warning"] = str(exc)
        livery_project["managedMoved"] = managed_moved
        livery_project["remainingFileCount"] = _livery_project_remaining_file_count(project_folder)

    manifest = {
        "action": "trash_my_skin",
        "trashedAt": datetime.now().isoformat(timespec="seconds"),
        "skin": entry,
        "deactivation": deactivation,
        "road_plate_cleanup": road_plate_cleanup,
        "moved": moved,
        "missing": missing,
        "savedcars_quarantine": savedcars_quarantine,
        "livery_project": livery_project,
        "loc_changes": [
            {
                "path": game_rel_from_disk(game_root, Path(str(row.get("path")))) or str(row.get("path")),
                "changed": bool(row.get("changed")),
            }
            for row in loc_changes
        ],
    }
    del skins[index]
    actor_cleanup: dict[str, Any] = {}
    try:
        actor_cleanup = cleanup_actor_numberplate_variant_for_deleted_skin(entry, skins, writer=writer)
    except Exception as exc:
        actor_cleanup = {"changed": False, "error": str(exc)}
    manifest["actor_cleanup"] = actor_cleanup
    saved = save_my_skins(skins)
    safety_refresh = refresh_multiplayer_safety_after_my_skin_entry_change(entry, game_root, "trash_my_skin", bypass_liverylab_safety)
    manifest_warning = ""
    try:
        writer.write_bytes(
            trash_dir / "trash_manifest.json",
            json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False).encode("utf-8"),
            label="trash manifest",
            backup=False,
        )
    except Exception as exc:
        manifest_warning = str(exc)
    return {
        "item": entry,
        "trashDir": trash_dir,
        "moved": moved,
        "missing": missing,
        "savedCarsQuarantine": savedcars_quarantine,
        "liveryProject": livery_project,
        "deactivation": deactivation,
        "roadPlateCleanup": road_plate_cleanup,
        "locChanges": loc_changes,
        "actorCleanup": actor_cleanup,
        "settingsPath": saved,
        "safetyRefresh": safety_refresh,
        "manifestWarning": manifest_warning,
        "writer": writer.to_dict(),
    }


def list_numberplate_templates(root: str | Path | None = None) -> dict[str, list[str]]:
    # Legacy AC EVO base-template scanner. It is only useful for dormant
    # numberplate game-export/debug paths and should not drive normal startup UI.
    templates = numberplate_core.list_numberplate_templates(root)
    try:
        custom = list_custom_numberplate_assets(root)
    except Exception:
        return templates
    custom_rels: set[str] = set()
    for bucket in ("backgrounds", "fonts"):
        for item in custom.get(bucket, []) or []:
            for key in ("assetRel", "gameTextureRel"):
                rel = normalize_game_rel_path(str(item.get(key) or "").strip()).lower()
                if rel:
                    custom_rels.add(rel)
    if not custom_rels:
        return templates
    return {
        "fonts": [rel for rel in templates.get("fonts", []) if normalize_game_rel_path(str(rel)).lower() not in custom_rels],
        "backgrounds": [rel for rel in templates.get("backgrounds", []) if normalize_game_rel_path(str(rel)).lower() not in custom_rels],
    }


def _custom_numberplate_usage_for_asset(root: Path | None, asset_type: str, asset_id: str, manifest: dict[str, Any]) -> list[dict[str, Any]]:
    asset_rel = _numberplate_custom_asset_rel(asset_type, asset_id, manifest)
    if not asset_rel:
        return []
    rows: list[dict[str, Any]] = []
    for skin in load_my_skins():
        if root is not None:
            skin_root_text = str(skin.get("game_root") or "").strip()
            if not skin_root_text:
                continue
            try:
                if normalize_game_root(skin_root_text).resolve() != root.resolve():
                    continue
            except Exception:
                continue
        variant = skin.get("numberplate_variant") if isinstance(skin.get("numberplate_variant"), dict) else {}
        if not variant or not _numberplate_variant_uses_asset(variant, asset_type, asset_rel):
            continue
        rows.append(
            {
                "skinKey": str(skin.get("key") or ""),
                "displayName": str(skin.get("display_name") or skin.get("skin_id") or skin.get("key") or ""),
                "skinId": str(skin.get("skin_id") or ""),
                "carModel": str(skin.get("car_model") or ""),
                "active": skin.get("active") is not False,
            }
        )
    rows.sort(key=lambda row: (str(row.get("carModel") or "").lower(), str(row.get("displayName") or "").lower()))
    return rows


def _attach_custom_numberplate_usage(assets: dict[str, Any], root: str | Path | None = None) -> dict[str, Any]:
    game_root: Path | None = None
    if root:
        try:
            game_root = normalize_game_root(root)
        except Exception:
            game_root = None
    for asset_type, bucket in (("background", "backgrounds"), ("font", "fonts")):
        for item in assets.get(bucket, []) or []:
            asset_id = str(item.get("id") or "").strip()
            item["assetRel"] = _numberplate_custom_asset_rel(asset_type, asset_id, item) if asset_id else ""
            item["usedBy"] = _custom_numberplate_usage_for_asset(game_root, asset_type, asset_id, item) if asset_id else []
    return assets


def list_custom_numberplate_assets(root: str | Path | None = None) -> dict[str, Any]:
    assets = numberplate_core.list_custom_numberplate_assets(get_livery_library_root())
    return _attach_custom_numberplate_usage(assets, root)


def list_numberplate_studio_templates() -> dict[str, Any]:
    return numberplate_template_core.list_numberplate_studio_templates(get_livery_library_root())


def save_numberplate_studio_template(payload: dict[str, Any]) -> dict[str, Any]:
    payload = payload if isinstance(payload, dict) else {}
    if NUMBERPLATE_STUDIO_GAME_EXPORT_ENABLED:
        backgrounds = payload.get("backgrounds") if isinstance(payload.get("backgrounds"), dict) else {}
        missing_local: list[str] = []
        for key, label in (("leftDoor", "Left door"), ("rightDoor", "Right door"), ("hood", "Hood")):
            ref = backgrounds.get(key) if isinstance(backgrounds.get(key), dict) else {}
            if not str(ref.get("assetId") or "").strip():
                missing_local.append(f"{label} background")
        font_ref = payload.get("font") if isinstance(payload.get("font"), dict) else {}
        if not str(font_ref.get("assetId") or "").strip():
            missing_local.append("Font")
        if missing_local:
            raise ValueError("Numberplate Studio templates can only be saved with local assets. Missing local assets: " + ", ".join(missing_local))
    result = numberplate_template_core.save_numberplate_studio_template(get_livery_library_root(), payload)
    root = str(payload.get("root") or payload.get("gameRoot") or "").strip()
    if root and bool(payload.get("publishToGame")) and NUMBERPLATE_STUDIO_GAME_EXPORT_ENABLED:
        publish = publish_numberplate_studio_template_font(root, result)
        result = _numberplate_studio_template(str(result.get("id") or "")) or result
        result["publish"] = publish
    result["affectedSkins"] = (
        rebake_my_skins_for_numberplate_template(str(result.get("id") or ""))
        if NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED
        else {"updatedCount": 0, "updated": [], "skipped": [], "errors": [], "disabled": True}
    )
    return result


def get_numberplate_studio_template_usage(template_id: str) -> dict[str, Any]:
    clean_template_id = str(template_id or "").strip()
    if not clean_template_id:
        return {"templateId": "", "skinCount": 0, "usedBySkins": [], "assetCount": 0, "assets": []}
    template = _numberplate_studio_template(clean_template_id)
    asset_refs = _numberplate_template_asset_refs(template) if isinstance(template, dict) else []
    used_by_skins: list[dict[str, Any]] = []
    for entry in load_my_skins():
        if not isinstance(entry, dict):
            continue
        variant = entry.get("numberplate_variant") if isinstance(entry.get("numberplate_variant"), dict) else {}
        bake = entry.get("numberplate_bake") if isinstance(entry.get("numberplate_bake"), dict) else {}
        sources: list[str] = []
        if _numberplate_template_matches(variant.get("template_id"), clean_template_id):
            sources.append("skin numberplate variant")
        if _numberplate_template_matches(bake.get("template_id"), clean_template_id):
            sources.append("baked numberplate files")
        if not sources:
            continue
        used_by_skins.append({
            "skinKey": str(entry.get("key") or ""),
            "label": str(entry.get("display_name") or entry.get("skin_id") or entry.get("key") or clean_template_id),
            "carModel": str(entry.get("car_model") or ""),
            "skinId": str(entry.get("skin_id") or ""),
            "active": bool(entry.get("active", True)),
            "sources": sources,
        })
    used_by_skins.sort(key=lambda row: (str(row.get("carModel", "")).lower(), str(row.get("label", "")).lower()))
    return {
        "templateId": clean_template_id,
        "templateName": str((template or {}).get("name") or clean_template_id) if isinstance(template, dict) else clean_template_id,
        "templateExists": isinstance(template, dict),
        "skinCount": len(used_by_skins),
        "usedBySkins": _json_ready(used_by_skins),
        "assetCount": len(asset_refs),
        "assets": _json_ready(asset_refs),
        "assetsDeletedOnTemplateDelete": False,
    }


def delete_numberplate_studio_template(template_id: str) -> dict[str, Any]:
    usage = get_numberplate_studio_template_usage(template_id)
    result = numberplate_template_core.delete_numberplate_studio_template(get_livery_library_root(), template_id)
    result["usage"] = usage
    result["assetsDeleted"] = False
    result["assetsPolicy"] = "Template assets are kept because they can be shared by other templates."
    return result



NUMBERPLATE_TEMPLATE_PACKAGE_TYPE = "numberplate_template_package"
TYRE_BRAND_PACKAGE_TYPE = "tyre_brand_package"
DRIVER_PROFILE_PACKAGE_TYPE = "driver_profile_package"
CUSTOM_GRID_PACKAGE_TYPE = "custom_grid_package"


def _numberplate_template_package_root(out_dir: str | Path | None = None, root: str | Path | None = None) -> Path:
    if out_dir:
        return Path(out_dir).expanduser().resolve()
    return get_livery_package_export_root(root or get_default_root_folder() or Path.cwd()) / "Numberplates"


def get_numberplate_template_export_root(root: str | Path | None = None) -> str:
    return str(_numberplate_template_package_root(None, root))


def get_numberplate_png_export_root() -> str:
    return str(get_livery_library_root())


NUMBERPLATE_DOWNLOAD_TEMPLATE_FILES = (
    "background_template.png",
    "numbers_template.png",
)

TYRE_BRAND_DOWNLOAD_TEMPLATE_FILES = (
    "tyre_racing_template_1_c.png",
    "tyre_racing_template_1_blur_c.png",
)


def _same_file_content(left: Path, right: Path) -> bool:
    try:
        return hashlib.sha256(left.read_bytes()).hexdigest() == hashlib.sha256(right.read_bytes()).hexdigest()
    except Exception:
        return False


def download_numberplate_templates() -> dict[str, Any]:
    target_root = get_livery_projects_root() / "NumberplateTemplates"
    target_root.mkdir(parents=True, exist_ok=True)
    items: list[dict[str, Any]] = []
    missing: list[str] = []
    for name in NUMBERPLATE_DOWNLOAD_TEMPLATE_FILES:
        source = _web_asset_path("numberplate_templates", name)
        if not source or not source.exists():
            missing.append(name)
            continue
        target = target_root / name
        status = "copied"
        backup = ""
        if target.exists():
            if _same_file_content(source, target):
                status = "current"
            else:
                backup_path = backup_file(target)
                backup = str(backup_path) if backup_path else ""
                shutil.copy2(source, target)
                status = "updated"
        else:
            shutil.copy2(source, target)
        items.append({
            "name": name,
            "status": status,
            "source": str(source),
            "target": str(target),
            "backup": backup,
        })
    if missing:
        raise FileNotFoundError(f"Missing numberplate template assets: {', '.join(missing)}")
    return {
        "folder": str(target_root),
        "items": _json_ready(items),
    }


def download_tyre_brand_templates() -> dict[str, Any]:
    target_root = get_livery_projects_root() / "TyreBrands"
    target_root.mkdir(parents=True, exist_ok=True)
    items: list[dict[str, Any]] = []
    missing: list[str] = []
    for name in TYRE_BRAND_DOWNLOAD_TEMPLATE_FILES:
        source = _web_asset_path("tyre_brand_templates", name)
        if not source or not source.exists():
            missing.append(name)
            continue
        target = target_root / name
        status = "copied"
        backup = ""
        if target.exists():
            if _same_file_content(source, target):
                status = "current"
            else:
                backup_path = backup_file(target)
                backup = str(backup_path) if backup_path else ""
                shutil.copy2(source, target)
                status = "updated"
        else:
            shutil.copy2(source, target)
        items.append({
            "name": name,
            "status": status,
            "source": str(source),
            "target": str(target),
            "backup": backup,
        })
    if missing:
        raise FileNotFoundError(f"Missing tyre brand template assets: {', '.join(missing)}")
    return {
        "folder": str(target_root),
        "items": _json_ready(items),
    }


def _tyre_brand_package_root(out_dir: str | Path | None = None, root: str | Path | None = None) -> Path:
    if out_dir:
        return Path(out_dir).expanduser().resolve()
    return get_livery_package_export_root(root or get_default_root_folder() or Path.cwd()) / "TyreBrands"


def get_tyre_brand_package_export_root(root: str | Path | None = None) -> str:
    return str(_tyre_brand_package_root(None, root))


def _driver_profile_package_export_root(out_dir: str | Path | None = None, root: str | Path | None = None) -> Path:
    if out_dir:
        return Path(out_dir).expanduser().resolve()
    return get_livery_package_export_root(root or get_default_root_folder() or Path.cwd()) / "Drivers"


def get_driver_profile_package_export_root(root: str | Path | None = None) -> str:
    return str(_driver_profile_package_export_root(None, root))


def _custom_grid_package_export_root(out_dir: str | Path | None = None, root: str | Path | None = None) -> Path:
    if out_dir:
        return Path(out_dir).expanduser().resolve()
    return get_livery_package_export_root(root or get_default_root_folder() or Path.cwd()) / "CustomGrids"


def get_custom_grid_package_export_root(root: str | Path | None = None) -> str:
    return str(_custom_grid_package_export_root(None, root))


def _numberplate_package_clean_id(value: Any, fallback: str = "numberplate_template") -> str:
    text = sanitize_slug(str(value or fallback))
    return text[:80] or fallback


def _numberplate_package_unique_id(parent: Path, preferred: str) -> str:
    base = _numberplate_package_clean_id(preferred)
    candidate = base
    index = 2
    while (parent / candidate).exists():
        candidate = f"{base}_import" if index == 2 else f"{base}_import_{index}"
        index += 1
    return candidate


def _numberplate_package_copy_json(value: Any) -> Any:
    return json.loads(json.dumps(_json_ready(value), ensure_ascii=False))


def _numberplate_package_source_png_name(value: Any = None) -> str:
    name = Path(str(value or "source.png").replace("\\", "/")).name.strip()
    if not name.lower().endswith(".png"):
        name = "source.png"
    clean = re.sub(r"[^A-Za-z0-9_.-]+", "_", name).strip("._-")
    return clean if clean.lower().endswith(".png") else "source.png"


def _numberplate_package_member_path(package_root: Path, rel_path: Any, label: str) -> Path:
    raw_text = str(rel_path or "").strip()
    raw_unix = raw_text.replace("\\", "/")
    if re.match(r"^[A-Za-z]:", raw_unix) or raw_unix.startswith("/"):
        raise ValueError(f"Package {label} path is unsafe: {raw_text}")
    rel_text = normalize_game_rel_path(raw_text)
    if not rel_text:
        raise ValueError(f"Package {label} path is missing")
    rel_disk = Path(*split_content_path(rel_text))
    if rel_disk.is_absolute() or ".." in rel_disk.parts:
        raise ValueError(f"Package {label} path is unsafe: {rel_text}")
    path = (package_root / rel_disk).resolve()
    try:
        path.relative_to(package_root.resolve())
    except ValueError as exc:
        raise ValueError(f"Package {label} path escapes the package: {rel_text}") from exc
    return path


def _numberplate_package_clean_ref(ref: Any, asset_map: dict[tuple[str, str], dict[str, str]] | None = None) -> dict[str, Any]:
    source = ref if isinstance(ref, dict) else {}
    asset_type = str(source.get("type") or "").strip().lower()
    asset_id = str(source.get("assetId") or source.get("id") or "").strip()
    mapped = (asset_map or {}).get((asset_type, asset_id.lower())) if asset_type and asset_id else None
    return {
        "type": asset_type,
        "assetId": mapped.get("id", asset_id) if mapped else asset_id,
        "rel": normalize_game_rel_path(mapped.get("rel", "") if mapped else str(source.get("rel") or source.get("assetRel") or source.get("path") or "")),
        "label": str(source.get("label") or source.get("name") or (mapped or {}).get("id") or asset_id or source.get("rel") or "").strip(),
    }


def _numberplate_package_clean_asset_manifest(manifest: dict[str, Any], new_id: str | None = None) -> dict[str, Any]:
    data = manifest if isinstance(manifest, dict) else {}
    asset_id = _numberplate_package_clean_id(new_id or data.get("id") or data.get("name") or "asset")
    asset_type = str(data.get("type") or "").strip().lower()
    if asset_type not in {"background", "font"}:
        raise ValueError(f"Unsupported numberplate package asset type: {asset_type or '-'}")
    clean = {
        "formatVersion": int(data.get("formatVersion") or 1),
        "type": asset_type,
        "id": asset_id,
        "name": str(data.get("name") or asset_id).strip() or asset_id,
        "status": "local",
        "active": False,
        "createdAt": str(data.get("createdAt") or datetime.now().isoformat(timespec="seconds")),
        "updatedAt": datetime.now().isoformat(timespec="seconds"),
        "baseTemplate": normalize_game_rel_path(str(data.get("baseTemplate") or "").strip()),
        "sourcePng": _numberplate_package_source_png_name(data.get("sourcePng")),
    }
    if isinstance(data.get("png"), dict):
        clean["png"] = _numberplate_package_copy_json(data.get("png"))
    return clean


def _numberplate_package_clean_template_manifest(template: dict[str, Any], new_id: str | None = None, asset_map: dict[tuple[str, str], dict[str, str]] | None = None) -> dict[str, Any]:
    data = template if isinstance(template, dict) else {}
    template_id = _numberplate_package_clean_id(new_id or data.get("id") or data.get("name") or "template")
    backgrounds = data.get("backgrounds") if isinstance(data.get("backgrounds"), dict) else {}
    clean = {
        "formatVersion": int(data.get("formatVersion") or 1),
        "type": "template",
        "id": template_id,
        "name": str(data.get("name") or template_id).strip() or template_id,
        "status": "local",
        "createdAt": str(data.get("createdAt") or datetime.now().isoformat(timespec="seconds")),
        "updatedAt": datetime.now().isoformat(timespec="seconds"),
        "backgrounds": {
            "leftDoor": _numberplate_package_clean_ref(backgrounds.get("leftDoor"), asset_map),
            "rightDoor": _numberplate_package_clean_ref(backgrounds.get("rightDoor"), asset_map),
            "hood": _numberplate_package_clean_ref(backgrounds.get("hood"), asset_map),
        },
        "font": _numberplate_package_clean_ref(data.get("font"), asset_map),
        "fontSource": str(data.get("fontSource") or "").strip(),
        "evoFontRel": normalize_game_rel_path(str(data.get("evoFontRel") or "").strip()),
        "textLayer": _numberplate_package_copy_json(data.get("textLayer") or numberplate_template_core.DEFAULT_TEXT_LAYER),
        "textColor": _numberplate_package_copy_json(data.get("textColor") or data.get("text_color") or numberplate_template_core.DEFAULT_TEXT_COLOR),
        "surfaceTextLayers": _numberplate_package_copy_json(data.get("surfaceTextLayers") if isinstance(data.get("surfaceTextLayers"), dict) else {}),
        "testNumber": re.sub(r"\D+", "", str(data.get("testNumber") or "250"))[:3] or "250",
    }
    clean["text_color"] = _numberplate_package_copy_json(clean["textColor"])
    preview_png = Path(str(data.get("previewPng") or "")).name.strip()
    if preview_png:
        clean["previewPng"] = preview_png
    return clean


def _numberplate_package_asset_manifest(asset_type: str, asset_id: str) -> dict[str, Any] | None:
    return _numberplate_asset_manifest_by_id(asset_type, asset_id)


def _numberplate_package_asset_source(asset_manifest: dict[str, Any]) -> Path | None:
    asset_dir = str(asset_manifest.get("assetDir") or "").strip()
    source_name = str(asset_manifest.get("sourcePng") or "source.png").strip() or "source.png"
    if asset_dir:
        candidate = Path(asset_dir) / source_name
        if candidate.exists():
            return candidate
    source_path = str(asset_manifest.get("sourcePngPath") or "").strip()
    if source_path and Path(source_path).exists():
        return Path(source_path)
    return None


def _numberplate_template_preview_source(template: dict[str, Any]) -> Path | None:
    preview_path = str(template.get("previewPngPath") or "").strip()
    if preview_path and Path(preview_path).exists():
        return Path(preview_path)
    asset_dir = str(template.get("assetDir") or "").strip()
    preview_name = Path(str(template.get("previewPng") or "preview.png").replace("\\", "/")).name
    if asset_dir and preview_name:
        candidate = Path(asset_dir) / preview_name
        if candidate.exists():
            return candidate
    return None


def _numberplate_template_asset_refs(template: dict[str, Any]) -> list[dict[str, str]]:
    refs: list[dict[str, str]] = []
    backgrounds = template.get("backgrounds") if isinstance(template.get("backgrounds"), dict) else {}
    for ref in [backgrounds.get("leftDoor"), backgrounds.get("rightDoor"), backgrounds.get("hood"), template.get("font")]:
        if not isinstance(ref, dict):
            continue
        asset_type = str(ref.get("type") or "").strip().lower()
        asset_id = str(ref.get("assetId") or ref.get("id") or "").strip()
        if asset_type in {"background", "font"} and asset_id:
            row = {"type": asset_type, "id": asset_id}
            if row not in refs:
                refs.append(row)
    return refs


def export_numberplate_template_package(template_id: str, out_dir: str | Path | None = None, root: str | Path | None = None) -> dict[str, Any]:
    template = _numberplate_studio_template(template_id)
    if not isinstance(template, dict):
        raise FileNotFoundError(f"Numberplate template not found: {template_id}")
    clean_id = _numberplate_package_clean_id(template.get("id") or template_id)
    package_name = short_export_package_name("plate", template.get("name") or clean_id, clean_id)
    export_root = _numberplate_template_package_root(out_dir, root)
    package_root = export_root / package_name
    if package_root.exists():
        shutil.rmtree(package_root)
    package_root.mkdir(parents=True, exist_ok=True)

    template_dir = package_root / "templates" / clean_id
    template_dir.mkdir(parents=True, exist_ok=True)
    template_manifest = _numberplate_package_clean_template_manifest(template, clean_id)
    preview_source = _numberplate_template_preview_source(template)
    template_preview_rel = ""
    if preview_source is not None:
        preview_name = Path(str(template_manifest.get("previewPng") or "preview.png")).name
        template_manifest["previewPng"] = preview_name
        shutil.copy2(preview_source, template_dir / preview_name)
        template_preview_rel = normalize_game_rel_path(str((template_dir / preview_name).relative_to(package_root)))
    (template_dir / "manifest.json").write_text(json.dumps(template_manifest, indent=2, ensure_ascii=False), encoding="utf-8")

    copied_assets: list[dict[str, Any]] = []
    missing_assets: list[dict[str, Any]] = []
    for ref in _numberplate_template_asset_refs(template):
        asset_type = ref["type"]
        asset_id = ref["id"]
        asset_manifest = _numberplate_package_asset_manifest(asset_type, asset_id)
        if not isinstance(asset_manifest, dict):
            missing_assets.append({"type": asset_type, "id": asset_id, "reason": "manifest missing"})
            continue
        source_png = _numberplate_package_asset_source(asset_manifest)
        if source_png is None:
            missing_assets.append({"type": asset_type, "id": asset_id, "reason": "source PNG missing"})
            continue
        bucket = "fonts" if asset_type == "font" else "backgrounds"
        target_dir = package_root / "assets" / bucket / _numberplate_package_clean_id(asset_id)
        target_dir.mkdir(parents=True, exist_ok=True)
        clean_manifest = _numberplate_package_clean_asset_manifest(asset_manifest, asset_id)
        (target_dir / "manifest.json").write_text(json.dumps(clean_manifest, indent=2, ensure_ascii=False), encoding="utf-8")
        shutil.copy2(source_png, target_dir / str(clean_manifest.get("sourcePng") or "source.png"))
        copied_assets.append({
            "type": asset_type,
            "id": asset_id,
            "path": normalize_game_rel_path(str((target_dir / "manifest.json").relative_to(package_root))),
            "source": normalize_game_rel_path(str((target_dir / str(clean_manifest.get("sourcePng") or "source.png")).relative_to(package_root))),
        })

    package_manifest = {
        "formatVersion": 1,
        "type": NUMBERPLATE_TEMPLATE_PACKAGE_TYPE,
        "app": {"name": APP_NAME, "version": APP_VERSION},
        "exportedAt": datetime.now().isoformat(timespec="seconds"),
        "template": {
            "id": clean_id,
            "name": str(template.get("name") or clean_id),
            "path": f"templates\\{clean_id}\\manifest.json",
            "preview": template_preview_rel,
        },
        "assets": copied_assets,
        "missing": missing_assets,
    }
    manifest_path = package_root / "manifest.json"
    manifest_path.write_text(json.dumps(package_manifest, indent=2, ensure_ascii=False), encoding="utf-8")
    zip_path = package_zip.export_zip_path(export_root, package_name)
    package_zip.zip_directory(package_root, zip_path)
    shutil.rmtree(package_root, ignore_errors=True)
    return {
        "packageRoot": str(zip_path),
        "packagePath": str(zip_path),
        "manifestPath": "manifest.json",
        "template": package_manifest["template"],
        "copiedAssets": copied_assets,
        "copiedAssetCount": len(copied_assets),
        "missing": missing_assets,
        "missingCount": len(missing_assets),
        "zip": True,
    }


def _inspect_numberplate_template_package_root(package_root: Path, package_path: str | Path | None = None) -> dict[str, Any]:
    validate_package_import_structure(
        package_root,
        {"manifest.json", "templates", "assets"},
        "Numberplate template package",
    )
    manifest_path = package_root / "manifest.json"
    if not manifest_path.exists():
        raise FileNotFoundError(f"manifest.json not found in package folder: {package_root}")
    manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    if not isinstance(manifest, dict) or manifest.get("type") != NUMBERPLATE_TEMPLATE_PACKAGE_TYPE:
        raise ValueError("Numberplate template package manifest is invalid")
    template_info = manifest.get("template") if isinstance(manifest.get("template"), dict) else {}
    template_rel = str(template_info.get("path") or "").strip()
    template_path = _numberplate_package_member_path(
        package_root,
        template_rel or f"templates\\{_numberplate_package_clean_id(template_info.get('id') or 'template')}\\manifest.json",
        "template",
    )
    assets = manifest.get("assets") if isinstance(manifest.get("assets"), list) else []
    missing: list[dict[str, Any]] = []
    if not template_path.exists():
        missing.append({"kind": "template", "path": normalize_game_rel_path(str(template_path.relative_to(package_root)))})
    else:
        try:
            template_manifest = json.loads(template_path.read_text(encoding="utf-8"))
        except Exception:
            template_manifest = {}
        preview_name = Path(str((template_manifest if isinstance(template_manifest, dict) else {}).get("previewPng") or "")).name.strip()
        if preview_name:
            preview_rel = normalize_game_rel_path(str(template_path.parent.relative_to(package_root) / preview_name))
            preview_path = _numberplate_package_member_path(package_root, preview_rel, "template preview")
            if not preview_path.exists():
                missing.append({"kind": "template preview", "path": preview_rel})
    for asset in assets:
        if not isinstance(asset, dict):
            continue
        for key in ("path", "source"):
            rel = str(asset.get(key) or "").strip()
            if not rel:
                missing.append({"kind": f"asset {key}", "path": "", "id": asset.get("id", ""), "type": asset.get("type", ""), "reason": "path missing"})
                continue
            path = _numberplate_package_member_path(package_root, rel, f"asset {key}")
            if not path.exists():
                missing.append({"kind": f"asset {key}", "path": rel, "id": asset.get("id", ""), "type": asset.get("type", "")})
    return {
        "packageRoot": str(package_root),
        "manifestPath": str(manifest_path),
        "manifest": manifest,
        "template": template_info,
        "assets": assets,
        "assetCount": len(assets),
        "missing": missing,
        "missingCount": len(missing),
        "packagePath": str(package_path or package_root),
        "zip": package_zip.is_zip_package(package_path or ""),
    }


def inspect_numberplate_template_package(package_dir: str | Path) -> dict[str, Any]:
    source = Path(package_dir).expanduser()
    with package_zip.package_root(source, get_package_import_temp_root(), allow_folder=True) as package_root:
        return _inspect_numberplate_template_package_root(package_root, source)


def _numberplate_package_move_existing(path: Path) -> str:
    if not path.exists():
        return ""
    trash_root = path.parent / "_trash"
    trash_root.mkdir(parents=True, exist_ok=True)
    stamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
    target = trash_root / f"{path.name}.{stamp}"
    shutil.move(str(path), str(target))
    return str(target)


def import_numberplate_template_package(package_dir: str | Path, conflict_mode: str = "rename") -> dict[str, Any]:
    source_package = Path(package_dir).expanduser()
    with package_zip.package_root(source_package, get_package_import_temp_root(), allow_folder=True) as package_root:
        return _import_numberplate_template_package_root(package_root, source_package, conflict_mode)


def _import_numberplate_template_package_root(package_root: Path, source_package: Path, conflict_mode: str = "rename") -> dict[str, Any]:
    info = _inspect_numberplate_template_package_root(package_root, source_package)
    if info.get("missingCount"):
        raise FileNotFoundError("Numberplate template package is incomplete")
    package_root = Path(info["packageRoot"])
    template_info = info["template"]
    template_path = _numberplate_package_member_path(
        package_root,
        str(template_info.get("path") or "") or f"templates\\{_numberplate_package_clean_id(template_info.get('id') or 'template')}\\manifest.json",
        "template",
    )
    template_manifest = json.loads(template_path.read_text(encoding="utf-8"))
    if not isinstance(template_manifest, dict):
        raise ValueError("Template manifest is invalid")

    library_root = get_livery_library_root()
    numberplate_root = library_root / "Numberplates"
    asset_map: dict[tuple[str, str], dict[str, str]] = {}
    imported_assets: list[dict[str, Any]] = []
    moved_existing: list[dict[str, Any]] = []
    mode = str(conflict_mode or "rename").strip().lower()
    replace_existing = mode in {"replace", "overwrite"}

    for asset in info.get("assets", []) or []:
        if not isinstance(asset, dict):
            continue
        asset_type = str(asset.get("type") or "").strip().lower()
        if asset_type not in {"background", "font"}:
            continue
        bucket = "fonts" if asset_type == "font" else "backgrounds"
        source_manifest_path = _numberplate_package_member_path(package_root, str(asset.get("path") or ""), "asset manifest")
        source_manifest = json.loads(source_manifest_path.read_text(encoding="utf-8"))
        old_id = _numberplate_package_clean_id(source_manifest.get("id") or asset.get("id") or "asset")
        target_base = numberplate_root / bucket
        target_id = old_id if replace_existing else _numberplate_package_unique_id(target_base, old_id)
        target_dir = target_base / target_id
        assert_package_import_target(target_dir, "numberplate package asset directory")
        if replace_existing and target_dir.exists():
            moved_existing.append({"kind": "asset", "type": asset_type, "id": target_id, "trashDir": _numberplate_package_move_existing(target_dir)})
        target_dir.mkdir(parents=True, exist_ok=True)
        clean_manifest = _numberplate_package_clean_asset_manifest(source_manifest, target_id)
        source_png_rel = str(asset.get("source") or "").strip() or f"assets\\{bucket}\\{old_id}\\{clean_manifest.get('sourcePng') or 'source.png'}"
        source_png = _numberplate_package_member_path(package_root, source_png_rel, "asset source")
        target_png = target_dir / str(clean_manifest.get("sourcePng") or "source.png")
        copy_package_import_file(source_png, target_png, "numberplate package asset source")
        assert_package_import_target(target_dir / "manifest.json", "numberplate package asset manifest")
        (target_dir / "manifest.json").write_text(json.dumps(clean_manifest, indent=2, ensure_ascii=False), encoding="utf-8")
        rel = normalize_game_rel_path(str(clean_manifest.get("baseTemplate") or ""))
        asset_map[(asset_type, old_id.lower())] = {"id": target_id, "rel": rel}
        imported_assets.append({"type": asset_type, "id": target_id, "sourceId": old_id, "assetDir": str(target_dir)})

    template_root = numberplate_root / "templates"
    old_template_id = _numberplate_package_clean_id(template_manifest.get("id") or template_info.get("id") or "template")
    target_template_id = old_template_id if replace_existing else _numberplate_package_unique_id(template_root, old_template_id)
    target_template_dir = template_root / target_template_id
    assert_package_import_target(target_template_dir, "numberplate package template directory")
    if replace_existing and target_template_dir.exists():
        moved_existing.append({"kind": "template", "id": target_template_id, "trashDir": _numberplate_package_move_existing(target_template_dir)})
    target_template_dir.mkdir(parents=True, exist_ok=True)
    clean_template = _numberplate_package_clean_template_manifest(template_manifest, target_template_id, asset_map)
    assert_package_import_target(target_template_dir / "manifest.json", "numberplate package template manifest")
    (target_template_dir / "manifest.json").write_text(json.dumps(clean_template, indent=2, ensure_ascii=False), encoding="utf-8")
    preview_name = Path(str(template_manifest.get("previewPng") or "")).name.strip()
    if preview_name:
        preview_source = _numberplate_package_member_path(package_root, normalize_game_rel_path(str(template_path.parent.relative_to(package_root) / preview_name)), "template preview")
        if preview_source.exists():
            copy_package_import_file(preview_source, target_template_dir / preview_name, "numberplate package template preview")
    affected = (
        rebake_my_skins_for_numberplate_template(target_template_id)
        if replace_existing and NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED
        else {"updatedCount": 0, "updated": [], "skipped": [], "errors": [], "disabled": not NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED}
    )
    return {
        "packageRoot": str(package_root),
        "template": {**clean_template, "assetDir": str(target_template_dir), "manifestPath": str(target_template_dir / "manifest.json")},
        "templateId": target_template_id,
        "sourceTemplateId": old_template_id,
        "importedAssets": imported_assets,
        "importedAssetCount": len(imported_assets),
        "movedExisting": moved_existing,
        "conflictMode": "replace" if replace_existing else "rename",
        "affectedSkins": affected,
        "packagePath": str(source_package),
        "zip": package_zip.is_zip_package(source_package),
    }


def _numberplate_template_matches(value: Any, template_id: str) -> bool:
    return str(value or "").strip().lower() == str(template_id or "").strip().lower()


def _numberplate_template_surface_keys_for_rebake(entry: dict[str, Any], variant: dict[str, Any], bake: dict[str, Any]) -> list[str]:
    return ["hood", "leftDoor", "rightDoor"]


def rebake_my_skins_for_numberplate_template(template_id: str) -> dict[str, Any]:
    clean_template_id = str(template_id or "").strip()
    if not clean_template_id:
        return {"updated": [], "skipped": [], "errors": []}
    skins = load_my_skins()
    updated: list[dict[str, Any]] = []
    skipped: list[dict[str, Any]] = []
    errors: list[dict[str, Any]] = []
    changed = False
    for index, entry in enumerate(skins):
        if not isinstance(entry, dict):
            continue
        variant = entry.get("numberplate_variant") if isinstance(entry.get("numberplate_variant"), dict) else {}
        bake = entry.get("numberplate_bake") if isinstance(entry.get("numberplate_bake"), dict) else {}
        uses_template = _numberplate_template_matches(variant.get("template_id"), clean_template_id) or _numberplate_template_matches(bake.get("template_id"), clean_template_id)
        if not uses_template:
            continue
        label = str(entry.get("display_name") or entry.get("skin_id") or entry.get("key") or clean_template_id)
        start_number = str(bake.get("start_number") or variant.get("start_number") or entry.get("start_number") or "").strip()
        if not start_number:
            skipped.append({"skinKey": entry.get("key"), "label": label, "reason": "missing start number"})
            continue
        if _entry_local_root(entry) is None:
            skipped.append({"skinKey": entry.get("key"), "label": label, "reason": "missing local skin root"})
            continue
        try:
            surface_keys = _numberplate_template_surface_keys_for_rebake(entry, variant, bake)
            rebaked = _bake_my_skin_numberplate_template(entry, clean_template_id, start_number, surface_keys)
            if not rebaked:
                skipped.append({"skinKey": entry.get("key"), "label": label, "reason": "template produced no bake"})
                continue
            entry["numberplate_bake"] = _json_ready(rebaked)
            entry["start_number"] = str(rebaked.get("start_number") or start_number)
            variant["template_id"] = clean_template_id
            entry["numberplate_variant"] = _json_ready(variant)
            _mark_my_skin_game_sync_required(entry, "Numberplate template changed", "save_numberplate_studio_template")
            skins[index] = entry
            updated.append({"skinKey": entry.get("key"), "label": label, "surfaces": list((rebaked.get("surfaces") or {}).keys())})
            changed = True
        except Exception as exc:
            errors.append({"skinKey": entry.get("key"), "label": label, "error": str(exc)})
    saved = save_my_skins(skins) if changed else None
    return {
        "updated": _json_ready(updated),
        "updatedCount": len(updated),
        "skipped": _json_ready(skipped),
        "errors": _json_ready(errors),
        "settingsPath": str(saved) if saved else "",
    }


def _numberplate_studio_template(template_id: str | None) -> dict[str, Any] | None:
    text = str(template_id or "").strip()
    if not text:
        return None
    return numberplate_template_core.get_numberplate_studio_template(get_livery_library_root(), text)


def _numberplate_studio_template_manifest_path(template: dict[str, Any]) -> Path | None:
    manifest_text = str(template.get("manifestPath") or "").strip()
    if manifest_text:
        return Path(manifest_text)
    asset_dir = str(template.get("assetDir") or "").strip()
    if asset_dir:
        return Path(asset_dir) / numberplate_template_core.TEMPLATE_MANIFEST_FILENAME
    template_id = str(template.get("id") or "").strip()
    if not template_id:
        return None
    return get_livery_library_root() / "Numberplates" / "templates" / sanitize_slug(template_id) / numberplate_template_core.TEMPLATE_MANIFEST_FILENAME


def _write_numberplate_studio_template_manifest(template: dict[str, Any]) -> None:
    manifest_path = _numberplate_studio_template_manifest_path(template)
    if manifest_path is None:
        return
    manifest_path.parent.mkdir(parents=True, exist_ok=True)

    def clean_persisted(value: Any) -> Any:
        if isinstance(value, dict):
            return {
                key: clean_persisted(item)
                for key, item in value.items()
                if key not in {"assetDir", "manifestPath", "publish", "sourcePngPath", "sourcePath", "pngPath"}
            }
        if isinstance(value, list):
            return [clean_persisted(item) for item in value]
        return value

    clean = {
        key: clean_persisted(value)
        for key, value in template.items()
        if key not in {"assetDir", "manifestPath", "publish"}
    }
    manifest_path.write_text(json.dumps(clean, indent=2, ensure_ascii=False), encoding="utf-8")


def _numberplate_template_published_font_path(template_id: str | None) -> str:
    template = _numberplate_studio_template(template_id)
    if not isinstance(template, dict):
        return ""
    for key in ("publishedFont", "gameFont"):
        ref = template.get(key)
        if isinstance(ref, dict):
            rel = normalize_game_rel_path(str(ref.get("rel") or ref.get("assetRel") or ref.get("path") or "").strip())
            if rel:
                return rel
    return ""


def _numberplate_template_font_base_path(template_id: str | None) -> str:
    template = _numberplate_studio_template(template_id)
    if not isinstance(template, dict):
        return ""
    font_ref = template.get("font") if isinstance(template.get("font"), dict) else {}
    return _numberplate_template_ref_base_rel(font_ref, "font")


def _numberplate_template_ref_source_png(ref: Any, asset_type: str) -> Path | None:
    if not isinstance(ref, dict):
        return None
    for key in ("sourcePngPath", "sourcePath", "pngPath"):
        value = str(ref.get(key) or "").strip()
        if value and Path(value).exists():
            return Path(value)
    asset_id = str(ref.get("assetId") or ref.get("id") or "").strip()
    manifest = _numberplate_asset_manifest_by_id(asset_type, asset_id) if asset_id else None
    if isinstance(manifest, dict):
        for key in ("sourcePngPath", "sourcePath"):
            value = str(manifest.get(key) or "").strip()
            if value and Path(value).exists():
                return Path(value)
        asset_dir = str(manifest.get("assetDir") or "").strip()
        source_name = str(manifest.get("sourcePng") or "source.png").strip()
        if asset_dir:
            candidate = Path(asset_dir) / source_name
            if candidate.exists():
                return candidate
    return None


def _numberplate_template_ref_base_rel(ref: Any, asset_type: str) -> str:
    if not isinstance(ref, dict):
        return ""
    asset_id = str(ref.get("assetId") or ref.get("id") or "").strip()
    manifest = _numberplate_asset_manifest_by_id(asset_type, asset_id) if asset_id else None
    if isinstance(manifest, dict):
        for key in ("baseTemplate", "gameTextureRel", "assetRel", "textureRel", "texture", "rel"):
            rel = normalize_game_rel_path(str(manifest.get(key) or "").strip())
            if rel:
                return rel
    return normalize_game_rel_path(str(ref.get("rel") or ref.get("assetRel") or ref.get("path") or "").strip())


def _numberplate_template_font_source_png(template: dict[str, Any]) -> Path | None:
    template_manifest = _numberplate_studio_template_manifest_path(template)
    template_dir = template_manifest.parent if template_manifest else None
    for key in ("publishedFont", "gameFont", "font"):
        ref = template.get(key)
        source = _numberplate_template_ref_source_png(ref, "font")
        if source:
            return source
        if template_dir and isinstance(ref, dict):
            source_name = str(ref.get("sourcePng") or "").strip()
            if source_name:
                candidate = template_dir / Path(str(source_name).replace("\\", "/")).name
                if candidate.exists():
                    return candidate
    return None


def _numberplate_template_surface_layer(template: dict[str, Any], surface_key: str) -> dict[str, float]:
    text_layer = _numberplate_template_text_layer(template)
    surface_layers = template.get("surfaceTextLayers") if isinstance(template.get("surfaceTextLayers"), dict) else {}
    layer = surface_layers.get(surface_key) if isinstance(surface_layers.get(surface_key), dict) else None
    return numberplate_render.coerce_text_layer(layer, text_layer)


def _numberplate_template_background_ref(template: dict[str, Any], surface_key: str) -> dict[str, Any]:
    backgrounds = template.get("backgrounds") if isinstance(template.get("backgrounds"), dict) else {}
    ref = backgrounds.get(surface_key)
    return ref if isinstance(ref, dict) else {}


def _numberplate_template_background_rel(template: dict[str, Any], surface_key: str) -> str:
    ref = _numberplate_template_background_ref(template, surface_key)
    return normalize_game_rel_path(str(ref.get("rel") or ref.get("assetRel") or ref.get("path") or "").strip())


def render_numberplate_studio_template_surface(
    template_id: str,
    surface_key: str,
    number: str,
    output_path: str | Path | None = None,
) -> dict[str, Any]:
    template = _numberplate_studio_template(template_id)
    if not isinstance(template, dict):
        raise FileNotFoundError(f"Numberplate template not found: {template_id}")
    clean_surface = str(surface_key or "").strip()
    if clean_surface not in numberplate_render.SURFACE_KEYS:
        raise ValueError(f"Unsupported numberplate template surface: {surface_key}")
    backgrounds = template.get("backgrounds") if isinstance(template.get("backgrounds"), dict) else {}
    background_ref = backgrounds.get(clean_surface) if isinstance(backgrounds.get(clean_surface), dict) else {}
    background_png = _numberplate_template_ref_source_png(background_ref, "background")
    if not background_png:
        raise FileNotFoundError(f"Numberplate template background PNG not found for {clean_surface}")
    font_png = _numberplate_template_font_source_png(template)
    if not font_png:
        raise FileNotFoundError(f"Numberplate template font PNG not found: {template_id}")
    render_number = numberplate_render.clean_number(number, str(template.get("testNumber") or "250"))
    if output_path is None:
        output_path = (
            get_livery_library_root()
            / "Numberplates"
            / "rendered_templates"
            / sanitize_slug(str(template.get("id") or template_id))
            / clean_surface
            / f"{render_number}.png"
        )
    result = numberplate_render.render_numberplate_png(
        background_png,
        output_path,
        font_path=font_png,
        number=render_number,
        text_layer=_numberplate_template_surface_layer(template, clean_surface),
        text_color=template.get("textColor") or template.get("text_color"),
        surface=clean_surface,
    )
    if not result.rendered_text:
        raise RuntimeError(f"Numberplate template text could not be rendered: {template_id}")
    return {
        "templateId": str(template.get("id") or template_id),
        "surface": result.surface,
        "number": result.number,
        "outputPath": str(result.output_path),
        "backgroundPath": str(result.background_path),
        "fontPath": str(result.font_path) if result.font_path else "",
        "textLayer": _json_ready(result.text_layer),
        "textColor": _json_ready(result.text_color),
        "renderedText": result.rendered_text,
    }


def _baked_numberplate_surface_rel(entry: dict[str, Any], template_id: str, surface_key: str, base_texture: Path) -> str:
    car_model = sanitize_slug(str(entry.get("car_model") or "car")).lower()
    skin_id = sanitize_slug(str(entry.get("skin_id") or "skin")).lower()
    clean_template = sanitize_slug(str(template_id or "template")).lower()
    clean_surface = sanitize_slug(str(surface_key or "surface")).lower()
    file_name = f"numberplate_{clean_template}.texture"
    return normalize_game_rel_path(
        f"content\\cars\\common_assets\\numberplates\\liverylab_baked\\{car_model}\\{skin_id}\\{clean_template}\\{clean_surface}\\{file_name}"
    )


def _write_local_baked_numberplate_texture(
    game_root: Path,
    local_root: Path,
    source_png: Path,
    base_texture_rel: str,
    target_texture_rel: str,
) -> dict[str, Any]:
    base_texture = _disk_from_game_rel(game_root, base_texture_rel)
    if base_texture is None or not base_texture.exists():
        raise FileNotFoundError(f"Numberplate base background texture not found: {base_texture_rel}")
    base_mips = base_texture.with_suffix(".texturemips")
    if not base_mips.exists():
        raise FileNotFoundError(f"Numberplate base background .texturemips not found: {base_mips}")
    target_texture = local_root / Path(*split_content_path(normalize_skin_asset_rel_path(target_texture_rel)))
    target_mips = target_texture.with_suffix(".texturemips")
    target_mips_rel = normalize_game_rel_path(str(Path(target_texture_rel).with_suffix(".texturemips")))

    desc = parse_texture_descriptor(base_texture)
    format_name, auto_note = choose_auto_format(base_texture, desc, base_mips.stat().st_size)
    target_mips_count = int(desc.get("mips") or 1)
    base_data = base_texture.read_bytes()
    old_group = base_texture.parent.name
    new_group = target_texture.parent.name
    patched_texture = patch_strings_recursive(
        base_data,
        {
            old_group: new_group,
            old_group.lower(): new_group.lower(),
            old_group.upper(): new_group.upper(),
            old_group.title(): new_group,
            old_group.capitalize(): new_group,
        },
    )
    with tempfile.TemporaryDirectory(prefix="liverylab_baked_plate_") as tmp:
        dds_path = Path(tmp) / f"{target_texture.stem}.dds"
        convert_image_to_dds_with_texconv(source_png, dds_path, format_name, None, target_mips_count, (int(desc["width"]), int(desc["height"])))
        descriptor_texture = Path(tmp) / target_texture.name
        descriptor_texture.write_bytes(patched_texture)
        mips_data = texturemips_bytes_from_dds(descriptor_texture, dds_path)
    target_texture.parent.mkdir(parents=True, exist_ok=True)
    texture_backup = safe_write_bytes(target_texture, patched_texture)
    mips_backup = safe_write_bytes(target_mips, mips_data)
    return {
        "textureRel": normalize_game_rel_path(target_texture_rel),
        "mipsRel": target_mips_rel,
        "format": format_name,
        "note": auto_note,
    }


def _bake_my_skin_numberplate_template(
    entry: dict[str, Any],
    template_id: str | None,
    start_number: str | None,
    surface_keys: list[str] | tuple[str, ...],
) -> dict[str, Any] | None:
    if not template_id:
        return None
    template = _numberplate_studio_template(template_id)
    if not isinstance(template, dict):
        raise FileNotFoundError(f"Numberplate template not found: {template_id}")
    local_root = _entry_local_root(entry)
    if local_root is None:
        raise FileNotFoundError("Local skin root is required for baked numberplates")
    game_root = normalize_game_root(str(entry.get("game_root", "")))
    render_number = numberplate_render.clean_number(start_number, str(template.get("testNumber") or entry.get("start_number") or "250"))
    clean_template = sanitize_slug(str(template.get("id") or template_id)).lower()
    render_root = local_root / "liverylab_generated" / "numberplates" / clean_template / render_number
    surfaces: dict[str, Any] = {}
    for surface_key in surface_keys:
        if surface_key not in numberplate_render.SURFACE_KEYS:
            continue
        background_ref = _numberplate_template_background_ref(template, surface_key)
        background_png = _numberplate_template_ref_source_png(background_ref, "background")
        background_rel = _numberplate_template_background_rel(template, surface_key)
        base_background_rel = _numberplate_template_ref_base_rel(background_ref, "background")
        if not background_png or not background_rel:
            continue
        base_texture = _disk_from_game_rel(game_root, base_background_rel)
        if base_texture is None or not base_texture.exists():
            raise FileNotFoundError(f"Numberplate template base background texture not found: {base_background_rel or background_rel}")
        png_path = render_root / f"{surface_key}.png"
        render_result = render_numberplate_studio_template_surface(
            str(template.get("id") or template_id),
            surface_key,
            render_number,
            png_path,
        )
        texture_rel = _baked_numberplate_surface_rel(entry, str(template.get("id") or template_id), surface_key, base_texture)
        texture_result = _write_local_baked_numberplate_texture(game_root, local_root, png_path, base_background_rel, texture_rel)
        png_rel = game_rel_from_disk(local_root, png_path) or normalize_skin_asset_rel_path(str(png_path.relative_to(local_root)))
        background_png_rel = game_rel_from_disk(get_livery_library_root(), background_png) or ""
        surfaces[surface_key] = {
            **texture_result,
            "pngPath": png_rel,
            "backgroundRel": background_rel,
            "baseBackgroundRel": base_background_rel,
            "backgroundPng": background_png_rel,
            "render": render_result,
        }
    if not surfaces:
        raise RuntimeError("No numberplate surfaces could be baked from the selected template")
    return {
        "mode": "baked_template",
        "template_id": str(template.get("id") or template_id),
        "start_number": render_number,
        "updatedAt": datetime.now().isoformat(timespec="seconds"),
        "surfaces": _json_ready(surfaces),
    }


def _numberplate_baked_surface_png_path(entry: dict[str, Any], surface: dict[str, Any]) -> Path | None:
    text = str(surface.get("pngPath") or surface.get("pngRel") or "").strip()
    if not text:
        return None
    path = Path(text)
    if path.is_absolute():
        return path
    local_path = _local_skin_asset_path(entry, text)
    return local_path or path


def _sync_local_numberplate_bake_to_game(entry: dict[str, Any], game_root: Path, writer: GameFileWriter) -> list[str]:
    bake = entry.get("numberplate_bake") if isinstance(entry.get("numberplate_bake"), dict) else {}
    surfaces = bake.get("surfaces") if isinstance(bake.get("surfaces"), dict) else {}
    local_root = _entry_local_root(entry)
    if not surfaces or local_root is None:
        return []
    synced: list[str] = []
    for surface_key, surface in surfaces.items():
        if not isinstance(surface, dict):
            continue
        count = 0
        for rel_key, label in (("textureRel", "texture"), ("mipsRel", "texturemips")):
            rel = normalize_skin_asset_rel_path(str(surface.get(rel_key) or ""))
            if not rel:
                continue
            source = local_root / Path(*split_content_path(rel))
            if not source.exists():
                continue
            target = game_root / Path(*split_content_path(rel))
            writer.copy_file(source, target, label=f"activate baked numberplate {surface_key} {label}")
            count += 1
        if count:
            synced.append(f"baked numberplate {surface_key} synced ({count} files)")
    return synced


NUMBERPLATE_ACTOR_TEXT_FIELD_KEYS = ("value_8", "value_9", "value_10", "value_11", "value_15", "value_16")
NUMBERPLATE_ACTOR_TEXT_FIELD_NUMBERS = {8, 9, 10, 11, 15, 16}

# Numberplate actor text fields, observed from in-game tests on 2026-05-13:
# - value_8: horizontal glyph scale. Lower values make each digit narrower, not closer together.
# - value_9: vertical/text scale. Lower values made the number height much smaller.
# - value_10: vertical text position. Lower values moved the whole number up.
# - value_11: changing 20 -> 0 had little/no visible effect on digit spacing.
# - value_15/value_16: coordinate/offset-like fields; keep inherited values unless explicitly edited.
# Keep these notes near the writer logic because the field names are reverse-engineered, not official.


def _coerce_numberplate_actor_text_fields(value: Any) -> dict[str, Any] | None:
    if not isinstance(value, dict):
        return None
    result: dict[str, Any] = {}
    for key in NUMBERPLATE_ACTOR_TEXT_FIELD_KEYS:
        if key not in value:
            continue
        raw = value.get(key)
        if raw in ("", None):
            result[key] = None
            continue
        try:
            result[key] = float(raw) if key == "value_15" else int(raw)
        except Exception:
            continue
    return result if result else None


def _numberplate_actor_text_fields_from_plate(plate: dict[str, Any] | None) -> dict[str, Any] | None:
    if not isinstance(plate, dict):
        return None
    return {key: plate.get(key) for key in NUMBERPLATE_ACTOR_TEXT_FIELD_KEYS}


def _numberplate_template_ref_rels(ref: Any) -> list[str]:
    if not isinstance(ref, dict):
        return []
    rels: list[str] = []
    for key in ("rel", "assetRel", "gameTextureRel", "textureRel", "path"):
        rel = normalize_game_rel_path(str(ref.get(key) or "").strip())
        if rel and rel not in rels:
            rels.append(rel)
    return rels


def _numberplate_template_surface_key(surface: int) -> str:
    return {1: "hood", 3: "leftDoor", 4: "rightDoor"}.get(int(surface), str(surface))


def _numberplate_template_explicit_text_fields(template: dict[str, Any], surface: int) -> dict[str, Any] | None:
    raw = template.get("gameTextFields") if isinstance(template.get("gameTextFields"), dict) else {}
    surface_key = _numberplate_template_surface_key(surface)
    candidates = [
        raw.get(str(surface)),
        raw.get(surface_key),
        raw.get(surface_key.replace("Door", "")),
        raw.get("default"),
    ]
    for candidate in candidates:
        fields = _coerce_numberplate_actor_text_fields(candidate)
        if fields:
            return fields
    return None


def _numberplate_template_apply_text_layer_fields(template: dict[str, Any], fields: dict[str, Any]) -> dict[str, Any]:
    result = dict(fields or {})
    # Do not map Studio spacing to actor fields until a real spacing field is identified.
    # Field 8 narrowed glyphs, field 9 changed height, field 10 moved the text vertically,
    # and field 11 had no visible spacing effect in game tests.
    return result


def _numberplate_template_inferred_text_fields(
    template: dict[str, Any],
    actor_data: bytes,
    backgrounds_by_surface: dict[int, str],
) -> dict[int, dict[str, Any]]:
    plates = _actor_extract_numberplates(actor_data)
    font_refs: list[str] = []
    font_refs.extend(_numberplate_template_ref_rels(template.get("font")))
    published = template.get("publishedFont") if isinstance(template.get("publishedFont"), dict) else {}
    game_font = template.get("gameFont") if isinstance(template.get("gameFont"), dict) else {}
    font_refs.extend(_numberplate_template_ref_rels(published.get("sourceFont") if isinstance(published.get("sourceFont"), dict) else {}))
    font_refs.extend(_numberplate_template_ref_rels(game_font.get("sourceFont") if isinstance(game_font.get("sourceFont"), dict) else {}))
    font_refs = list(dict.fromkeys(normalize_game_rel_path(rel).lower() for rel in font_refs if rel))

    result: dict[int, dict[str, Any]] = {}
    for surface in (1, 3, 4):
        explicit = _numberplate_template_explicit_text_fields(template, surface)
        if explicit:
            result[surface] = _numberplate_template_apply_text_layer_fields(template, explicit)
            continue
        background_rel = normalize_game_rel_path(str(backgrounds_by_surface.get(surface) or "")).lower()
        best_plate: dict[str, Any] | None = None
        best_score = 0
        for plate in plates:
            score = 0
            plate_background = normalize_game_rel_path(str(plate.get("background_path") or "")).lower()
            plate_font = normalize_game_rel_path(str(plate.get("font_path") or "")).lower()
            if background_rel and plate_background == background_rel:
                score += 4
            if plate_font and plate_font in font_refs:
                score += 3
            if background_rel and plate_background and Path(background_rel).parent.name.lower() == Path(plate_background).parent.name.lower():
                score += 1
            if score > best_score:
                best_score = score
                best_plate = plate
        if best_plate and best_score >= 3:
            fields = _numberplate_actor_text_fields_from_plate(best_plate)
            if fields:
                result[surface] = _numberplate_template_apply_text_layer_fields(template, fields)
    return result


def _numberplate_template_text_fields_by_surface(
    template_id: str | None,
    actor_data: bytes,
    hood_background_path: str | None = None,
    left_background_path: str | None = None,
    right_background_path: str | None = None,
) -> dict[int, dict[str, Any]]:
    template = _numberplate_studio_template(template_id)
    if not isinstance(template, dict):
        return {}
    backgrounds_by_surface = {
        1: normalize_game_rel_path(str(hood_background_path or "").strip()),
        3: normalize_game_rel_path(str(left_background_path or "").strip()),
        4: normalize_game_rel_path(str(right_background_path or "").strip()),
    }
    return _numberplate_template_inferred_text_fields(template, actor_data, backgrounds_by_surface)


def inspect_custom_numberplate_asset_usage(root: str | Path | None, asset_type: str, asset_id: str) -> dict[str, Any]:
    manifest = _numberplate_asset_manifest_for_deactivation(asset_type, asset_id)
    game_root: Path | None = None
    if root:
        try:
            game_root = normalize_game_root(root)
        except Exception:
            game_root = None
    asset_rel = _numberplate_custom_asset_rel(asset_type, asset_id, manifest)
    used_by = _custom_numberplate_usage_for_asset(game_root, asset_type, asset_id, manifest)
    return {
        "id": asset_id,
        "type": asset_type,
        "name": manifest.get("name") or asset_id,
        "assetRel": asset_rel,
        "fallbackRel": normalize_game_rel_path(str(manifest.get("baseTemplate") or "").strip()),
        "usedBy": used_by,
        "usedByCount": len(used_by),
    }


def create_custom_numberplate_asset(root: str | Path, asset_type: str, base_template: str | Path, name: str, png: str | Path, overwrite: bool = False) -> dict[str, Any]:
    if not root:
        raise ValueError("Root path is required")
    return numberplate_core.create_custom_numberplate_asset(
        library_root=get_livery_library_root(),
        game_root=root,
        asset_type=asset_type,
        base_template=base_template,
        name=name,
        png=png,
        overwrite=overwrite,
    )


def create_custom_numberplate_asset_from_game(root: str | Path, asset_type: str, base_template: str | Path, name: str, overwrite: bool = False) -> dict[str, Any]:
    if not root:
        raise ValueError("Root path is required")
    game_root = normalize_game_root(root)
    base_rel = normalize_game_rel_path(str(base_template or "").strip())
    if not base_rel:
        raise ValueError("AC EVO base template is required")
    base_texture = resolve_content_path(game_root, base_rel)
    if not base_texture.exists():
        raise FileNotFoundError(f"AC EVO base template not found: {base_rel}")
    with tempfile.TemporaryDirectory(prefix="liverylab_np_source_") as tmp:
        source_png = Path(tmp) / "source.png"
        convert_texture_to_png(base_texture, source_png, overwrite=True, create_dds=False)
        result = create_custom_numberplate_asset(
            root=game_root,
            asset_type=asset_type,
            base_template=base_rel,
            name=name,
            png=source_png,
            overwrite=overwrite,
        )
    result["source"] = "game"
    result["sourceBaseTemplate"] = base_rel
    return result


def replace_custom_numberplate_asset_png(root: str | Path, asset_type: str, asset_id: str, png: str | Path, refresh: bool = False) -> dict[str, Any]:
    if not root:
        raise ValueError("Root path is required")
    result = numberplate_core.replace_custom_numberplate_asset_png(
        library_root=get_livery_library_root(),
        game_root=root,
        asset_type=asset_type,
        asset_id=asset_id,
        png=png,
    )
    if refresh and result.get("active"):
        activated = activate_custom_numberplate_asset(root, asset_type, asset_id)
        activated["replacement"] = result
        activated["refreshed"] = True
        return activated
    result["refreshed"] = False
    return result


def activate_custom_numberplate_asset(root: str | Path, asset_type: str, asset_id: str) -> dict[str, Any]:
    if not root:
        raise ValueError("Root path is required")
    return numberplate_core.activate_custom_numberplate_asset(
        library_root=get_livery_library_root(),
        game_root=root,
        asset_type=asset_type,
        asset_id=asset_id,
        parse_texture_descriptor=parse_texture_descriptor,
        choose_auto_format=choose_auto_format,
        convert_image_to_dds_with_texconv=convert_image_to_dds_with_texconv,
        texturemips_bytes_from_dds=texturemips_bytes_from_dds,
    )


def _numberplate_asset_manifest_for_deactivation(asset_type: str, asset_id: str) -> dict[str, Any]:
    assets = numberplate_core.list_custom_numberplate_assets(get_livery_library_root())
    normalized_type = str(asset_type).strip().lower()
    if normalized_type not in {"font", "background"}:
        raise ValueError(f"Unsupported numberplate asset type: {asset_type}")
    bucket = "fonts" if normalized_type == "font" else "backgrounds"
    clean_id = str(asset_id or "").strip().lower()
    for item in assets.get(bucket, []) or []:
        if str(item.get("id") or "").strip().lower() == clean_id:
            return item
    raise FileNotFoundError(f"Custom numberplate asset not found: {asset_id}")


def _numberplate_custom_asset_rel(asset_type: str, asset_id: str, manifest: dict[str, Any]) -> str:
    explicit = str(manifest.get("gameTextureRel") or "").strip()
    if explicit:
        return normalize_game_rel_path(explicit)
    base_template = normalize_game_rel_path(str(manifest.get("baseTemplate") or "").strip())
    if not base_template:
        return ""
    folder = "fonts" if str(asset_type).strip().lower() == "font" else "backgrounds"
    file_name = Path(base_template).name
    return normalize_game_rel_path(f"content\\cars\\common_assets\\numberplates\\{folder}\\{asset_id}\\{file_name}")


def _numberplate_template_text_layer(template: dict[str, Any] | None) -> dict[str, float]:
    raw = template.get("textLayer") if isinstance(template, dict) and isinstance(template.get("textLayer"), dict) else {}
    defaults = {"x": 0.0, "y": 0.0, "scale": 0.3, "spacing": 0.02}
    values: dict[str, float] = {}
    for key, fallback in defaults.items():
        try:
            value = float(raw.get(key, fallback))
        except Exception:
            value = fallback
        if key == "scale":
            value = max(0.05, min(1.0, value))
        elif key == "spacing":
            value = max(-0.1, min(0.5, value))
        else:
            value = max(-1.0, min(1.0, value))
        values[key] = value
    return values


def _numberplate_asset_manifest_by_id(asset_type: str, asset_id: str) -> dict[str, Any] | None:
    clean_id = str(asset_id or "").strip().lower()
    if not clean_id:
        return None
    bucket = "fonts" if str(asset_type).strip().lower() == "font" else "backgrounds"
    assets = numberplate_core.list_custom_numberplate_assets(get_livery_library_root())
    for item in assets.get(bucket, []) or []:
        if str(item.get("id") or "").strip().lower() == clean_id:
            return item
    return None


def _numberplate_template_font_source(
    game_root: Path,
    local_root: Path,
    template: dict[str, Any],
    font_path: str | None,
) -> tuple[str, Path, Path]:
    font_ref = template.get("font") if isinstance(template.get("font"), dict) else {}
    asset_id = str(font_ref.get("assetId") or "").strip()
    asset_manifest = _numberplate_asset_manifest_by_id("font", asset_id) if asset_id else None
    source_png: Path | None = None
    base_rel = normalize_game_rel_path(str(font_path or font_ref.get("rel") or "").strip())
    if asset_manifest:
        manifest_source = Path(str(asset_manifest.get("assetDir") or "")) / str(asset_manifest.get("sourcePng") or "source.png")
        if manifest_source.exists():
            source_png = manifest_source
        base_rel = normalize_game_rel_path(str(asset_manifest.get("baseTemplate") or asset_manifest.get("gameTextureRel") or base_rel).strip())
    if not base_rel:
        raise ValueError("Numberplate template font path is missing")
    base_texture = game_root / Path(*split_content_path(base_rel))
    if not base_texture.exists():
        local_candidate = local_root / Path(*split_content_path(base_rel))
        if local_candidate.exists():
            base_texture = local_candidate
        else:
            raise FileNotFoundError(f"Numberplate template base font texture not found: {base_texture}")
    if source_png is None:
        cache_key = hashlib.sha1(base_rel.lower().encode("utf-8", errors="ignore")).hexdigest()[:12]
        source_png = get_livery_library_root() / "Numberplates" / "template_font_sources" / f"{cache_key}.png"
        if not source_png.exists():
            convert_texture_to_png(base_texture, source_png, overwrite=False, create_dds=False)
    if not source_png.exists():
        raise FileNotFoundError(f"Numberplate template font source PNG not found: {source_png}")
    return base_rel, base_texture, source_png


def _copy_numberplate_font_png(source_png: Path, target_png: Path) -> dict[str, Any]:
    try:
        from PIL import Image
    except Exception:
        target_png.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(source_png, target_png)
        return {"sourcePng": str(source_png), "targetPng": str(target_png), "normalized": False}
    with Image.open(source_png) as image:
        if image.size == (1024, 512):
            target_png.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(source_png, target_png)
            return {"sourcePng": str(source_png), "targetPng": str(target_png), "normalized": False}
        source = image.convert("RGBA")
        source = source.resize((1024, 512), getattr(getattr(Image, "Resampling", Image), "LANCZOS", Image.BICUBIC))
        target_png.parent.mkdir(parents=True, exist_ok=True)
        source.save(target_png, format="PNG")
    return {
        "sourcePng": str(source_png),
        "targetPng": str(target_png),
        "normalized": True,
    }


def publish_numberplate_studio_template_font(root: str | Path, template: dict[str, Any]) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    if not isinstance(template, dict):
        raise ValueError("Numberplate template payload is missing")
    template_id = sanitize_slug(str(template.get("id") or template.get("name") or "template")).lower()
    if not template_id:
        raise ValueError("Numberplate template id is missing")
    text_layer = _numberplate_template_text_layer(template)
    font_rel = normalize_game_rel_path(str((template.get("font") or {}).get("rel") if isinstance(template.get("font"), dict) else "").strip())
    base_rel, base_texture, source_png = _numberplate_template_font_source(game_root, game_root, template, font_rel)
    asset_id = sanitize_slug(f"liverylab_{template_id}")[:80].lower()
    target_rel = normalize_game_rel_path(f"content\\cars\\common_assets\\numberplates\\fonts\\{asset_id}\\{base_texture.name}")
    target_texture = game_root / Path(*split_content_path(target_rel))
    target_mips = target_texture.with_suffix(".texturemips")
    target_mips_rel = normalize_game_rel_path(str(Path(target_rel).with_suffix(".texturemips")))
    template_dir = Path(str(template.get("assetDir") or "")).resolve() if str(template.get("assetDir") or "").strip() else get_livery_library_root() / "Numberplates" / "templates" / template_id
    target_png = template_dir / "published_font_source.png"

    bake_info = _copy_numberplate_font_png(source_png, target_png)
    bake_info["textLayer"] = _json_ready(text_layer)
    bake_info["layoutSource"] = "font_copy"

    desc = parse_texture_descriptor(base_texture)
    base_mips = base_texture.with_suffix(".texturemips")
    if not base_mips.exists():
        raise FileNotFoundError(f"Base template .texturemips not found: {base_mips}")
    format_name, auto_note = choose_auto_format(base_texture, desc, base_mips.stat().st_size)
    target_mips_count = int(desc.get("mips") or 1)
    base_data = base_texture.read_bytes()
    old_group = base_texture.parent.name
    replacements = {
        old_group: asset_id,
        old_group.lower(): asset_id.lower(),
        old_group.upper(): asset_id.upper(),
        old_group.title(): asset_id,
        old_group.capitalize(): asset_id,
    }
    patched_texture = patch_strings_recursive(base_data, replacements)
    with tempfile.TemporaryDirectory(prefix="liverylab_template_font_") as tmp:
        dds_path = Path(tmp) / f"{asset_id}.dds"
        convert_image_to_dds_with_texconv(target_png, dds_path, format_name, None, target_mips_count, (1024, 512))
        descriptor_texture = Path(tmp) / base_texture.name
        descriptor_texture.write_bytes(patched_texture)
        mips_data = texturemips_bytes_from_dds(descriptor_texture, dds_path)

    writer = GameFileWriter(game_root, "publish numberplate studio template font", [target_texture.parent])
    changed = False
    if not target_texture.exists() or target_texture.read_bytes() != patched_texture:
        writer.write_bytes(target_texture, patched_texture, label="numberplate studio template font descriptor", backup=True)
        changed = True
    if not target_mips.exists() or target_mips.read_bytes() != mips_data:
        writer.write_bytes(target_mips, mips_data, label="numberplate studio template font mips", backup=True)
        changed = True

    now = datetime.now().isoformat(timespec="seconds")
    published_ref = {
        "type": "font",
        "assetId": asset_id,
        "rel": target_rel,
        "assetRel": target_rel,
        "mipsRel": target_mips_rel,
        "label": f"{template.get('name') or template_id} / published font",
        "sourcePng": target_png.name,
        "sourceFont": _json_ready(template.get("font") if isinstance(template.get("font"), dict) else {}),
        "textLayer": _json_ready(text_layer),
        "publishedAt": now,
    }
    template["publishedFont"] = published_ref
    template["gameFont"] = published_ref
    template["updatedAt"] = now
    template["publishedAt"] = now
    _write_numberplate_studio_template_manifest(template)
    return {
        "templateId": template_id,
        "changed": changed,
        "font": published_ref,
        "baseFontPath": base_rel,
        "bake": bake_info,
        "writer": writer.to_dict(),
        "note": auto_note,
    }


def _numberplate_variant_uses_asset(variant: dict[str, Any], asset_type: str, asset_rel: str) -> bool:
    needle = normalize_game_rel_path(asset_rel).lower()
    if not needle:
        return False
    if str(asset_type).strip().lower() == "font":
        return normalize_game_rel_path(str(variant.get("font_path") or "")).lower() == needle
    return any(
        normalize_game_rel_path(str(variant.get(key) or "")).lower() == needle
        for key in ("background_path", "hood_background_path", "left_background_path", "right_background_path")
    )


def _fallback_numberplate_update_payload(variant: dict[str, Any], asset_type: str, asset_rel: str, fallback_rel: str) -> dict[str, Any]:
    needle = normalize_game_rel_path(asset_rel).lower()
    mode = str(variant.get("mode") or "same").strip().lower()
    payload: dict[str, Any] = {
        "mode": "surface" if mode in {"surface", "surfaces", "per_surface", "per-surface"} else ("split" if mode == "split" else "same"),
        "font_path": str(variant.get("font_path") or ""),
        "background_path": str(variant.get("background_path") or ""),
        "hood_background_path": str(variant.get("hood_background_path") or ""),
        "left_background_path": str(variant.get("left_background_path") or ""),
        "right_background_path": str(variant.get("right_background_path") or ""),
        "text_color": variant.get("text_color") or variant.get("textColor"),
    }
    if str(asset_type).strip().lower() == "font":
        payload["font_path"] = fallback_rel
    else:
        for key in ("background_path", "hood_background_path", "left_background_path", "right_background_path"):
            if normalize_game_rel_path(str(payload.get(key) or "")).lower() == needle:
                payload[key] = fallback_rel
    return payload


def _restore_my_skin_numberplate_fallbacks(root: Path, asset_type: str, asset_id: str, asset_rel: str, fallback_rel: str) -> list[dict[str, Any]]:
    updates: list[dict[str, Any]] = []
    if not asset_rel or not fallback_rel:
        return updates
    for skin in load_my_skins():
        skin_root_text = str(skin.get("game_root") or "").strip()
        if not skin_root_text:
            continue
        try:
            if normalize_game_root(skin_root_text).resolve() != root.resolve():
                continue
        except Exception:
            continue
        variant = skin.get("numberplate_variant") if isinstance(skin.get("numberplate_variant"), dict) else {}
        if not variant or not _numberplate_variant_uses_asset(variant, asset_type, asset_rel):
            continue
        key = str(skin.get("key") or "").strip()
        if not key:
            continue
        payload = _fallback_numberplate_update_payload(variant, asset_type, asset_rel, fallback_rel)
        local_result = update_my_skin_numberplate(
            key,
            font_path=payload.get("font_path"),
            background_path=payload.get("background_path"),
            mode=payload.get("mode"),
            hood_background_path=payload.get("hood_background_path"),
            left_background_path=payload.get("left_background_path"),
            right_background_path=payload.get("right_background_path"),
            text_color=payload.get("text_color"),
        )
        game_result: dict[str, Any] | None = None
        if skin.get("active") is not False:
            game_result = set_my_skin_active(key, True)
        updates.append(
            {
                "skinKey": key,
                "displayName": skin.get("display_name") or skin.get("skin_id") or key,
                "active": skin.get("active") is not False,
                "fallback": fallback_rel,
                "local": local_result,
                "game": game_result,
            }
        )
    return updates


def deactivate_custom_numberplate_asset(root: str | Path, asset_type: str, asset_id: str) -> dict[str, Any]:
    if not root:
        raise ValueError("Root path is required")
    game_root = normalize_game_root(root)
    manifest = _numberplate_asset_manifest_for_deactivation(asset_type, asset_id)
    asset_rel = _numberplate_custom_asset_rel(asset_type, asset_id, manifest)
    fallback_rel = normalize_game_rel_path(str(manifest.get("baseTemplate") or "").strip())
    if not fallback_rel:
        raise RuntimeError("Custom numberplate asset has no base template fallback.")
    fallback_updates = _restore_my_skin_numberplate_fallbacks(game_root, asset_type, asset_id, asset_rel, fallback_rel)
    result = numberplate_core.deactivate_custom_numberplate_asset(
        library_root=get_livery_library_root(),
        game_root=game_root,
        asset_type=asset_type,
        asset_id=asset_id,
    )
    result["safeDeactivate"] = {
        "assetRel": asset_rel,
        "fallbackRel": fallback_rel,
        "updatedSkins": fallback_updates,
        "updatedSkinCount": len(fallback_updates),
    }
    return result


def create_numberplate_background(root: str | Path, source_background: str | Path, new_name: str, texturemips: str | Path | None = None, overwrite: bool = False) -> dict[str, Any]:
    return numberplate_core.create_numberplate_background(
        root=root,
        source_background=source_background,
        new_name=new_name,
        texturemips=texturemips,
        overwrite=overwrite,
    )


def add_browser_favorite(name: str, path: str) -> Path:
    favorite_name = str(name or "").strip()
    favorite_path = str(path or "").strip()
    if not favorite_name:
        raise ValueError("Favorite name is empty")
    if not favorite_path:
        raise ValueError("Favorite path is empty")
    target = Path(favorite_path).expanduser()
    if not target.is_dir():
        raise FileNotFoundError(f"Folder not found: {target}")
    favorites = load_browser_favorites()
    target_key = _browser_favorite_path_key(target)
    favorite_name = _unique_browser_favorite_name(favorite_name, favorites, target_key)
    for item in favorites:
        if _browser_favorite_path_key(item["path"]) == target_key:
            item["name"] = favorite_name
            item["path"] = str(target)
            return save_browser_favorites(favorites)
    favorites.append({"name": favorite_name, "path": str(target)})
    favorites.sort(key=lambda item: item["name"].lower())
    return save_browser_favorites(favorites)


def delete_browser_favorite(name: str) -> Path:
    favorite_name = str(name or "").strip()
    if not favorite_name:
        raise ValueError("Favorite name or path is empty")
    looks_like_path = any(sep in favorite_name for sep in ("\\", "/")) or ":" in favorite_name
    target_key = _browser_favorite_path_key(favorite_name) if looks_like_path else ""
    favorites = load_browser_favorites()
    remaining = [
        item
        for item in favorites
        if item["name"].lower() != favorite_name.lower()
        and (not target_key or _browser_favorite_path_key(item["path"]) != target_key)
    ]
    if len(remaining) == len(favorites):
        raise KeyError(f"Favorite not found: {favorite_name}")
    return save_browser_favorites(remaining)


def _uv_mesh_settings_key(mesh_path: str | Path, root: str | Path | None = None) -> str:
    mesh = Path(mesh_path)
    root_path = Path(root) if root else None
    if root_path:
        rel = content_rel_from_disk(root_path, mesh)
        if rel:
            return norm_slashes(rel).lower()
        try:
            return norm_slashes(str(mesh.resolve().relative_to(root_path.resolve()))).lower()
        except Exception:
            pass
    try:
        return norm_slashes(str(mesh.resolve())).lower()
    except Exception:
        return norm_slashes(str(mesh)).lower()


def load_uv_mesh_settings(mesh_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    settings = load_app_settings()
    uv_presets = settings.get("uv_presets", {})
    if not isinstance(uv_presets, dict):
        return {"mesh_key": _uv_mesh_settings_key(mesh_path, root), "last_used": None, "presets": {}}
    mesh_key = _uv_mesh_settings_key(mesh_path, root)
    entry = uv_presets.get(mesh_key, {})
    if not isinstance(entry, dict):
        entry = {}
    presets = entry.get("presets", {})
    return {
        "mesh_key": mesh_key,
        "last_used": entry.get("last_used"),
        "presets": presets if isinstance(presets, dict) else {},
    }


def _normalize_uv_view_state(state: dict[str, Any] | None) -> dict[str, Any]:
    data = state if isinstance(state, dict) else {}
    groups = data.get("groups", [])
    min_triangles = data.get("minTriangles", 100)
    try:
        min_triangles = max(1, int(min_triangles))
    except Exception:
        min_triangles = 100
    return {
        "groups": [str(group) for group in groups if str(group).strip()],
        "showTexture": bool(data.get("showTexture", True)),
        "outlineOnly": bool(data.get("outlineOnly", True)),
        "flipU": bool(data.get("flipU", False)),
        "flipV": bool(data.get("flipV", True)),
        "majorOnly": bool(data.get("majorOnly", False)),
        "minTriangles": min_triangles,
        "exportOverlay": bool(data.get("exportOverlay", True)),
    }


def save_uv_mesh_last_used(mesh_path: str | Path, root: str | Path | None, state: dict[str, Any]) -> Path:
    settings = load_app_settings()
    uv_presets = settings.get("uv_presets")
    if not isinstance(uv_presets, dict):
        uv_presets = {}
        settings["uv_presets"] = uv_presets
    mesh_key = _uv_mesh_settings_key(mesh_path, root)
    entry = uv_presets.get(mesh_key)
    if not isinstance(entry, dict):
        entry = {}
        uv_presets[mesh_key] = entry
    entry["last_used"] = _normalize_uv_view_state(state)
    if not isinstance(entry.get("presets"), dict):
        entry["presets"] = {}
    return save_app_settings(settings)


def save_uv_mesh_preset(mesh_path: str | Path, root: str | Path | None, preset_name: str, state: dict[str, Any]) -> Path:
    name = str(preset_name or "").strip()
    if not name:
        raise ValueError("Preset name is empty")
    settings = load_app_settings()
    uv_presets = settings.get("uv_presets")
    if not isinstance(uv_presets, dict):
        uv_presets = {}
        settings["uv_presets"] = uv_presets
    mesh_key = _uv_mesh_settings_key(mesh_path, root)
    entry = uv_presets.get(mesh_key)
    if not isinstance(entry, dict):
        entry = {}
        uv_presets[mesh_key] = entry
    presets = entry.get("presets")
    if not isinstance(presets, dict):
        presets = {}
        entry["presets"] = presets
    presets[name] = _normalize_uv_view_state(state)
    return save_app_settings(settings)


def delete_uv_mesh_preset(mesh_path: str | Path, root: str | Path | None, preset_name: str) -> Path:
    name = str(preset_name or "").strip()
    if not name:
        raise ValueError("Preset name is empty")
    settings = load_app_settings()
    uv_presets = settings.get("uv_presets")
    if not isinstance(uv_presets, dict):
        raise KeyError(f"Preset not found: {name}")
    mesh_key = _uv_mesh_settings_key(mesh_path, root)
    entry = uv_presets.get(mesh_key)
    if not isinstance(entry, dict):
        raise KeyError(f"Preset not found: {name}")
    presets = entry.get("presets")
    if not isinstance(presets, dict) or name not in presets:
        raise KeyError(f"Preset not found: {name}")
    del presets[name]
    return save_app_settings(settings)


def load_preview3d_project() -> dict[str, Any]:
    settings = load_app_settings()
    project = settings.get("preview3d_project", {})
    project = project if isinstance(project, dict) else {}
    rebased = install_relocation.rebase_managed_paths(project, get_portable_storage_root())
    if rebased != project:
        settings["preview3d_project"] = rebased
        try:
            save_app_settings(settings)
        except OSError:
            pass
    return preview3d.inspect_project(rebased)


def _save_livery_preview_project_preferences(project: dict[str, Any], normalized: dict[str, Any]) -> Path | None:
    project_path_text = str(project.get("projectPath") or "").strip()
    if not project_path_text:
        project_folder_text = str(project.get("projectFolder") or "").strip()
        if project_folder_text:
            project_path_text = str(Path(project_folder_text) / preview3d.PROJECT_FILE_NAME)
    if not project_path_text:
        return None
    project_path = Path(project_path_text)
    if not project_path.exists() and not project_path.parent.exists():
        return None
    data: dict[str, Any] = {}
    if project_path.exists():
        try:
            loaded = json.loads(project_path.read_text(encoding="utf-8"))
            data = loaded if isinstance(loaded, dict) else {}
        except Exception:
            data = {}
    data["bodyBaseColor"] = preview3d.clean_hex_color(normalized.get("bodyBaseColor"))
    data["materialFinish"] = _json_ready(normalized.get("materialFinish") or _normalize_livery_material_finish({}))
    hidden_materials = [
        str(row or "").strip()
        for row in (normalized.get("hiddenMaterialNames") or [])
        if str(row or "").strip()
    ]
    hidden_exterior = [
        str(row or "").strip()
        for row in (
            normalized.get("hiddenExteriorMaterialNames")
            or project.get("hiddenExteriorMaterialNames")
            or project.get("hiddenExteriorComponentMaterials")
            or []
        )
        if str(row or "").strip()
    ]
    if hidden_materials:
        data["hiddenMaterialNames"] = hidden_materials
    else:
        data.pop("hiddenMaterialNames", None)
    if hidden_exterior:
        data["hiddenExteriorMaterialNames"] = hidden_exterior
        data["hiddenExteriorComponentMaterials"] = hidden_exterior
        preview = data.get("variantPartPreview") if isinstance(data.get("variantPartPreview"), dict) else {}
        preview["hiddenExteriorComponentMaterials"] = hidden_exterior
        data["variantPartPreview"] = preview
    else:
        data.pop("hiddenExteriorMaterialNames", None)
        data.pop("hiddenExteriorComponentMaterials", None)
        preview = data.get("variantPartPreview") if isinstance(data.get("variantPartPreview"), dict) else {}
        if preview:
            preview.pop("hiddenExteriorComponentMaterials", None)
            data["variantPartPreview"] = preview
    data["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    project_path.parent.mkdir(parents=True, exist_ok=True)
    project_path.write_text(json.dumps(_json_ready(data), indent=2, ensure_ascii=False), encoding="utf-8")
    return project_path


def save_preview3d_project(project: dict[str, Any]) -> tuple[dict[str, Any], Path]:
    if (
        isinstance(project, dict)
        and str(project.get("projectType") or "").strip() == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY
        and str(project.get("skinKey") or "").strip()
    ):
        result = save_livery_body_base_color(project)
        saved_project = result.get("project") if isinstance(result.get("project"), dict) else {}
        saved_project = {
            **saved_project,
            "changed": result.get("changed") is True,
            "gameUpdateRequired": result.get("gameUpdateRequired") is True,
            "gameSync": result.get("gameSync") or {},
        }
        return saved_project, Path(str(result.get("settingsPath") or get_my_skins_registry_path()))
    settings = load_app_settings()
    normalized = preview3d.normalize_project(project)
    settings["preview3d_project"] = normalized
    _save_livery_preview_project_preferences(project if isinstance(project, dict) else {}, normalized)
    saved = save_app_settings(settings)
    return preview3d.inspect_project(normalized), saved


def save_livery_body_base_color(payload: dict[str, Any]) -> dict[str, Any]:
    data = payload if isinstance(payload, dict) else {}
    skin_key = str(data.get("skinKey") or "").strip()
    entry, skins, index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canEditMaterialFinish")
    if not (
        project_types.project_type_for_entry(entry) == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY
        and official_external_workflow.role_for_entry(entry) == official_external_workflow.ROLE_PROJECT
    ):
        project, saved = save_preview3d_project(data)
        return {
            "changed": True,
            "gameUpdateRequired": False,
            "project": project.get("project") if isinstance(project.get("project"), dict) else project,
            "settingsPath": str(saved),
        }

    project_folder = _entry_local_root(entry)
    if project_folder is None or not project_folder.exists() or not project_folder.is_dir():
        raise FileNotFoundError(f"Official project folder not found: {project_folder or ''}")
    color = preview3d.clean_hex_color(data.get("bodyBaseColor"))
    material_batch = _official_external_entry_material_batch(entry)
    material_surfaces = _official_external_entry_material_surfaces(entry)
    saved_car_color = _official_external_entry_saved_car_body_color(entry)
    saved_car_update: dict[str, Any] = {}
    if saved_car_color.get("supported"):
        if game_launcher.is_game_running():
            raise RuntimeError("Close AC EVO before applying Body Color to the SavedCar.")
        saved_car_update = saved_car_body_color.write(
            str(saved_car_color.get("path") or ""),
            normalize_game_root(str(entry.get("game_root") or get_default_root_folder())),
            color,
        )
        external_update = official_external_project_assets.clear_official_body_base_color(
            project_folder,
            material_batch=material_batch,
            material_surfaces=material_surfaces,
        )
        body_color_mode = "saved_car_synced"
        capability_source = str(saved_car_update.get("after", {}).get("source") or "saved_car")
        capability_reason = "Body Color is synchronized with the active AC EVO SavedCar"
    else:
        external_update = official_external_project_assets.write_official_body_base_color(
            project_folder,
            color,
            material_batch=material_batch,
            material_surfaces=material_surfaces,
        )
        body_color_mode = "official_external_red_channel"
        capability_source = "external_livery_json"
        capability_reason = "This car uses a fixed Official livery color"
    channels = official_external_project_assets.official_material_preview_config(project_folder, material_batch=material_batch)
    body_color_capability = {
        "mode": body_color_mode,
        "enabled": True,
        "source": capability_source,
        "reason": capability_reason,
        "materialBatch": external_update.get("materialBatch") or material_batch,
    }
    project_payload = {
        **data,
        "projectFolder": str(project_folder),
        "bodyBaseColor": color,
        "bodyBaseColorEnabled": True,
        "bodyBaseColorMode": body_color_mode,
        "bodyBaseColorCapability": body_color_capability,
        "officialMaterialChannels": channels,
    }
    normalized = preview3d.normalize_project(project_payload)
    project_path = _save_livery_preview_project_preferences(project_payload, normalized)
    _write_livery_preview_project_fields(project_path, {
        "bodyBaseColor": color,
        "bodyBaseColorEnabled": True,
        "bodyBaseColorMode": body_color_mode,
        "bodyBaseColorCapability": body_color_capability,
        "officialMaterialChannels": channels,
    })
    changed = external_update.get("changed") is True or saved_car_update.get("changed") is True
    game_update_required = external_update.get("changed") is True and bool(entry.get("active"))
    entry["body_base_color"] = color
    if game_update_required:
        _mark_my_skin_game_sync_required(entry, "In-game body color changed", "body_base_color")
    skins[index] = entry
    settings_path = save_my_skins(skins)
    manifest_update = _update_official_external_project_manifest(
        project_folder,
        official_external_project_assets.inspect_project(project_folder, material_batch=material_batch),
    )
    return {
        "changed": changed,
        "gameUpdateRequired": game_update_required,
        "bodyBaseColor": color,
        "project": {
            **normalized,
            "bodyBaseColor": color,
            "bodyBaseColorEnabled": True,
            "bodyBaseColorMode": body_color_mode,
            "bodyBaseColorCapability": body_color_capability,
            "officialMaterialChannels": channels,
        },
        "projectPath": str(project_path) if project_path else "",
        "settingsPath": str(settings_path),
        "externalUpdate": external_update,
        "savedCarUpdate": saved_car_update,
        "savedCarBodyColor": saved_car_update.get("after") if saved_car_update else saved_car_color,
        "officialManifest": manifest_update,
        "gameSync": entry.get("game_sync") or {},
        "item": entry,
    }


def inspect_preview3d_project(project: dict[str, Any]) -> dict[str, Any]:
    return preview3d.inspect_project(project)


def derive_preview3d_body_base_color(project: dict[str, Any]) -> dict[str, Any]:
    return preview3d.derive_body_base_color(project if isinstance(project, dict) else {})


BODY_BASE_COLOR_MODE_DESIGN_ENABLED = "design_enabled"
BODY_BASE_COLOR_MODE_BLANK_ENABLED = "blank_enabled"
BODY_BASE_COLOR_MODE_DISABLED = "disabled"
BODY_BASE_COLOR_MODE_UNKNOWN = "unknown"


def _design_ext_skin_body_base_color_state(design_data: bytes, target_materials: set[str] | None = None) -> dict[str, Any]:
    target_refs = target_materials or set()
    ext_slot_seen = False
    paint_ref_count = 0
    selector_active = False
    selector_seen = False
    slot_material = ""
    try:
        fields = parse_fields(design_data)
    except Exception as exc:
        return {"mode": BODY_BASE_COLOR_MODE_UNKNOWN, "enabled": False, "reason": f"design parse failed: {exc}"}
    for field in fields:
        if field.number != 3 or field.wire != 2 or not isinstance(field.value, (bytes, bytearray)):
            continue
        try:
            slot_fields = parse_fields(bytes(field.value))
        except Exception:
            continue
        if not _design_slot_targets_ext_skin(slot_fields, target_refs):
            continue
        ext_slot_seen = True
        for slot_field in slot_fields:
            if slot_field.wire != 2 or not isinstance(slot_field.value, (bytes, bytearray)):
                continue
            text = is_printable_utf8(bytes(slot_field.value))
            if slot_field.number == 3 and text:
                slot_material = text
            if text and _is_evo_paint_color_ref(text):
                paint_ref_count += 1
            if slot_field.number != 6:
                continue
            control_fields = _proto_roundtrip_fields(bytes(slot_field.value)) or []
            for control_field in control_fields:
                if control_field.wire == 2 and isinstance(control_field.value, (bytes, bytearray)):
                    control_text = is_printable_utf8(bytes(control_field.value))
                    if control_text and _is_evo_paint_color_ref(control_text):
                        paint_ref_count += 1
                    if control_field.number == 1:
                        selector_seen = True
                        if bytes(control_field.value):
                            selector_active = True
    if selector_active:
        return {
            "mode": BODY_BASE_COLOR_MODE_DESIGN_ENABLED,
            "enabled": True,
            "source": "design",
            "reason": "active EXT_SKIN paint selector",
            "paintRefCount": paint_ref_count,
            "slotMaterial": slot_material,
        }
    if ext_slot_seen:
        return {
            "mode": BODY_BASE_COLOR_MODE_DISABLED,
            "enabled": False,
            "source": "design",
            "reason": "EXT_SKIN paint selector is inactive" if selector_seen else "EXT_SKIN paint selector missing",
            "paintRefCount": paint_ref_count,
            "slotMaterial": slot_material,
        }
    return {"mode": BODY_BASE_COLOR_MODE_UNKNOWN, "enabled": False, "source": "design", "reason": "EXT_SKIN design slot not found"}


def _body_base_color_mode_for_livery_preview(
    entry: dict[str, Any],
    game_root: Path,
    project: dict[str, Any],
    entry_project_type: str,
    *,
    editable_preview: bool,
) -> dict[str, Any]:
    if entry_project_type == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
        channels = project.get("officialMaterialChannels") if isinstance(project.get("officialMaterialChannels"), dict) else {}
        if channels.get("available"):
            return {
                "mode": "official_external_red_channel",
                "enabled": True,
                "source": "external_livery_json",
                "reason": "official external red material channel",
                "materialBatch": channels.get("materialBatch") or "",
            }
        return {
            "mode": BODY_BASE_COLOR_MODE_UNKNOWN,
            "enabled": False,
            "source": "official_external",
            "reason": "official external material channel metadata is unavailable",
        }
    start_point = project.get("liveryStartPoint") if isinstance(project.get("liveryStartPoint"), dict) else {}
    if not start_point and isinstance(entry.get("livery_start_point"), dict):
        start_point = entry.get("livery_start_point")
    start_mode = str(start_point.get("mode") or "").strip().lower()
    if editable_preview and start_mode == livery_start_points.BLANK_START_POINT_ID:
        return {
            "mode": BODY_BASE_COLOR_MODE_BLANK_ENABLED,
            "enabled": True,
            "source": "advanced_blank",
            "reason": "advanced blank livery workspace",
        }
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    design_path = _skin_file_disk_path(game_root, entry, str(files.get("design") or ""))
    if design_path and design_path.exists():
        target_materials = _entry_ext_skin_material_refs(entry)
        state = _design_ext_skin_body_base_color_state(design_path.read_bytes(), target_materials)
        state["designPath"] = str(design_path)
        return state
    if editable_preview and not (entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}):
        return {
            "mode": BODY_BASE_COLOR_MODE_BLANK_ENABLED,
            "enabled": True,
            "source": "advanced_blank",
            "reason": "advanced livery without source-skin metadata",
        }
    return {
        "mode": BODY_BASE_COLOR_MODE_UNKNOWN,
        "enabled": False,
        "source": "preview_project",
        "reason": "no design metadata available",
    }


def _write_livery_preview_project_fields(project_path: str | Path | None, updates: dict[str, Any]) -> None:
    if not project_path:
        return
    path = Path(str(project_path))
    if not path.exists() or not path.is_file():
        return
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        if not isinstance(data, dict):
            return
        data.update(_json_ready(updates))
        data["updatedAt"] = datetime.now().isoformat(timespec="seconds")
        path.write_text(json.dumps(_json_ready(data), indent=2, ensure_ascii=False), encoding="utf-8")
    except Exception:
        return


LIVERY_MATERIAL_FINISH_DEFAULTS = {
    "metalness": 0.0,
    "roughness": 0.3,
    "clearCoat": 1.0,
    "clearCoatRoughness": 0.3,
}


LIVERY_MATERIAL_FINISH_PARAMS = {
    "metalness": "Base_Metalness",
    "roughness": "Base_Roughness",
    "clearCoat": "Base_ClearCoat",
    "clearCoatRoughness": "Base_ClearCoatRoughness",
}

LIVERY_MATERIAL_FINISH_TEXTURE_KEYS = dict(paint_only_livery.FINISH_KEY_TO_TEXTURE_KEY)


def _is_ext_skin_design_slot(slot_id: str | None) -> bool:
    normalized = str(slot_id or "").strip().upper().replace(" ", "_")
    return normalized == "EXT_SKIN" or normalized.startswith("EXT_SKIN_")


def _finish_clamped_float(value: Any, fallback: float) -> float:
    try:
        number = float(value)
    except Exception:
        number = fallback
    return max(0.0, min(number, 1.0))


def _normalize_livery_material_finish(data: dict[str, Any] | None) -> dict[str, Any]:
    raw = data if isinstance(data, dict) else {}
    return {
        key: _finish_clamped_float(raw.get(key), fallback)
        for key, fallback in LIVERY_MATERIAL_FINISH_DEFAULTS.items()
    } | {"scope": "base"}


def _livery_material_finish_from_material(material_path: Path, root: Path | None = None) -> dict[str, Any]:
    finish = dict(LIVERY_MATERIAL_FINISH_DEFAULTS)
    try:
        material = inspect_material(material_path, root=root)
    except Exception:
        return _normalize_livery_material_finish(finish)
    params = {
        str(param.get("name") or ""): param.get("value")
        for param in material.get("parameters", [])
        if isinstance(param, dict)
    }
    for finish_key, param_name in LIVERY_MATERIAL_FINISH_PARAMS.items():
        value = params.get(param_name)
        if isinstance(value, (int, float)):
            finish[finish_key] = float(value)
    return _normalize_livery_material_finish(finish)


def _local_livery_material_path(entry: dict[str, Any]) -> Path:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    material = _local_skin_asset_path(entry, str(files.get("material") or ""))
    if not material or not material.exists():
        raise FileNotFoundError("Skin-local EXT_SKIN material was not found")
    return material


def _entry_ext_skin_material_refs(entry: dict[str, Any]) -> set[str]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    refs: set[str] = set()
    material_ref = normalize_skin_asset_rel_path(str(files.get("material") or ""))
    if material_ref:
        refs.add(_normalized_material_ref(material_ref))
    for _kind, design_path in _skin_file_paths_for_kinds(entry, {"design"}):
        try:
            slot_materials = extract_design_slot_materials(design_path.read_bytes())
        except Exception:
            continue
        for slot_id, materials in slot_materials.items():
            if not _is_ext_skin_design_slot(slot_id):
                continue
            for material in materials:
                material_norm = normalize_skin_asset_rel_path(str(material or ""))
                if material_norm:
                    refs.add(_normalized_material_ref(material_norm))
    return refs


def _local_livery_material_paths(entry: dict[str, Any]) -> list[Path]:
    paths: list[Path] = []
    seen: set[str] = set()

    def add(path: Path | None) -> None:
        if not path or not path.exists() or not path.is_file():
            return
        key = str(path.resolve()).lower()
        if key in seen:
            return
        seen.add(key)
        paths.append(path)

    try:
        add(_local_livery_material_path(entry))
    except Exception:
        pass

    for material_ref in sorted(_entry_ext_skin_material_refs(entry)):
        add(_local_skin_asset_path(entry, material_ref))
    return paths


GT3RS_WEISSACH_STICKER_DELETE_MATERIAL = (
    "content\\cars\\ks_porsche_992_gt3_rs\\skins\\ext_opt_delete\\EXT_STICKERS_OPT_DELETE.material"
)
GT3RS_WEISSACH_STICKER_MATERIAL_REPLACEMENTS = {
    "content\\cars\\ks_porsche_992_gt3_rs\\skins\\ext_opt_weissach\\EXT_STICKERS_OPT_WEISSACH.material": GT3RS_WEISSACH_STICKER_DELETE_MATERIAL,
    "content\\cars\\ks_porsche_992_gt3_rs\\skins\\ext_opt_weissach\\EXT_STICKERS_2_OPT_WEISSACH.material": GT3RS_WEISSACH_STICKER_DELETE_MATERIAL,
}


def _ensure_gt3rs_weissach_stickers_removed(entry: dict[str, Any]) -> dict[str, Any]:
    car_content_path = normalize_game_rel_path(str(entry.get("car_content_path") or ""))
    if car_content_path.lower() != "content\\cars\\ks_porsche_992_gt3_rs":
        return {"changed": False, "patched": [], "replacements": {}, "reason": "not gt3rs"}

    exact_replacements = {
        _normalize_exact_text(old): norm_slashes(new)
        for old, new in GT3RS_WEISSACH_STICKER_MATERIAL_REPLACEMENTS.items()
    }
    patched: list[dict[str, Any]] = []
    for kind, path in _skin_file_paths_for_kinds(entry, {"design", "visual_preset", "carfinalstate", "carfinalstates"}):
        old_data = path.read_bytes()
        new_data = patch_strings_recursive_exact(old_data, exact_replacements)
        if new_data == old_data:
            continue
        backup = _write_local_skin_bytes_with_backup(path, new_data)
        patched.append({
            "kind": kind,
            "path": str(path),
            "backup": str(backup) if backup else "",
        })

    return {
        "changed": bool(patched),
        "patched": patched,
        "replacements": GT3RS_WEISSACH_STICKER_MATERIAL_REPLACEMENTS,
    }


GT3RS_WEISSACH_STICKER_PREVIEW_HIDDEN_MATERIALS = ["EXT_STICKERS", "EXT_STICKERS_2"]
GT3RS_STANDARD_STICKER_PREVIEW_HIDDEN_MATERIALS = ["EXT_STICKERS"]
GT3RS_STANDARD_SOURCE_CARDESIGN = (
    "content\\cars\\ks_porsche_992_gt3_rs\\skins\\design_gt3rs_1.cardesign"
)
GT3RS_WEISSACH_CARBON_EXT_SKIN_MATERIALS = [
    "EXT_SKIN_2_OPT_WEISSACH.material",
    "EXT_SKIN_4_OPT_WEISSACH.material",
    "EXT_SKIN_5_OPT_WEISSACH.material",
    "EXT_SKIN_6_OPT_WEISSACH.material",
]
CAYMAN_GT4RS_WEISSACH_CARBON_EXT_SKIN_MATERIALS = [
    "EXT_SKIN_2_OPT_WP.material",
    "EXT_SKIN_4_OPT_CARBON.material",
    "EXT_SKIN_5_OPT_CARBON.material",
]


def _gt3rs_weissach_preview_hidden_materials(entry: dict[str, Any]) -> list[str]:
    car_content_path = normalize_game_rel_path(str(entry.get("car_content_path") or ""))
    if car_content_path.lower() != "content\\cars\\ks_porsche_992_gt3_rs":
        return []
    delete_ref = _normalized_material_ref(GT3RS_WEISSACH_STICKER_DELETE_MATERIAL)
    for _kind, path in _skin_file_paths_for_kinds(entry, {"design", "visual_preset", "carfinalstate", "carfinalstates"}):
        try:
            data = path.read_bytes()
        except Exception:
            continue
        if delete_ref.encode("utf-8") in data.lower():
            return list(GT3RS_WEISSACH_STICKER_PREVIEW_HIDDEN_MATERIALS)
    return []


def _gt3rs_standard_preview_hidden_materials(entry: dict[str, Any]) -> list[str]:
    car_content_path = normalize_game_rel_path(str(entry.get("car_content_path") or ""))
    if car_content_path.lower() != "content\\cars\\ks_porsche_992_gt3_rs":
        return []
    source = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    files = entry.get("files") if isinstance(entry.get("files"), dict) else {}
    cardesign_refs = [
        str(source.get("cardesign") or ""),
        str(files.get("cardesign") or ""),
    ]
    standard_ref = normalize_game_rel_path(GT3RS_STANDARD_SOURCE_CARDESIGN).lower()
    for ref in cardesign_refs:
        if normalize_game_rel_path(ref).lower() == standard_ref:
            return list(GT3RS_STANDARD_STICKER_PREVIEW_HIDDEN_MATERIALS)
    return []


def _ensure_gt3rs_weissach_carbon_ext_skin_neutralized(entry: dict[str, Any]) -> dict[str, Any]:
    car_content_path = normalize_game_rel_path(str(entry.get("car_content_path") or ""))
    if car_content_path.lower() != "content\\cars\\ks_porsche_992_gt3_rs":
        return {"changed": False, "patched": [], "reason": "not gt3rs"}

    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    local_skin_folder = _local_skin_asset_path(entry, skin_rel)
    if not skin_rel or local_skin_folder is None or not local_skin_folder.exists():
        return {"changed": False, "patched": [], "reason": "local skin folder missing"}

    try:
        base_material = _local_livery_material_path(entry)
    except Exception as exc:
        return {"changed": False, "patched": [], "reason": str(exc)}
    if not base_material.exists() or not base_material.is_file():
        return {"changed": False, "patched": [], "reason": "base material missing"}

    base_data = base_material.read_bytes()
    patched: list[dict[str, Any]] = []
    for filename in GT3RS_WEISSACH_CARBON_EXT_SKIN_MATERIALS:
        target = local_skin_folder / filename
        if not target.exists() or not target.is_file():
            continue
        original = target.read_bytes()
        if original == base_data:
            continue
        backup = _write_local_skin_bytes_with_backup(target, base_data)
        patched.append({
            "path": str(target),
            "backup": str(backup) if backup else "",
        })

    return {
        "changed": bool(patched),
        "patched": patched,
        "source": str(base_material),
        "materials": list(GT3RS_WEISSACH_CARBON_EXT_SKIN_MATERIALS),
    }


def _ensure_cayman_gt4rs_weissach_carbon_ext_skin_neutralized(entry: dict[str, Any]) -> dict[str, Any]:
    car_content_path = normalize_game_rel_path(str(entry.get("car_content_path") or ""))
    if car_content_path.lower() != "content\\cars\\ks_porsche_718_cayman_gt4_rs":
        return {"changed": False, "patched": [], "reason": "not cayman gt4 rs"}

    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    local_skin_folder = _local_skin_asset_path(entry, skin_rel)
    if not skin_rel or local_skin_folder is None or not local_skin_folder.exists():
        return {"changed": False, "patched": [], "reason": "local skin folder missing"}

    try:
        base_material = _local_livery_material_path(entry)
    except Exception as exc:
        return {"changed": False, "patched": [], "reason": str(exc)}
    if not base_material.exists() or not base_material.is_file():
        return {"changed": False, "patched": [], "reason": "base material missing"}

    base_data = base_material.read_bytes()
    patched: list[dict[str, Any]] = []
    for filename in CAYMAN_GT4RS_WEISSACH_CARBON_EXT_SKIN_MATERIALS:
        target = local_skin_folder / filename
        if not target.exists() or not target.is_file():
            continue
        original = target.read_bytes()
        if original == base_data:
            continue
        backup = _write_local_skin_bytes_with_backup(target, base_data)
        patched.append({
            "path": str(target),
            "backup": str(backup) if backup else "",
        })

    return {
        "changed": bool(patched),
        "patched": patched,
        "source": str(base_material),
        "materials": list(CAYMAN_GT4RS_WEISSACH_CARBON_EXT_SKIN_MATERIALS),
    }


SF25_CAR_MODEL = "ks_ferrari_sf_25"
SF25_NEUTRAL_MATERIAL_SLOTS: dict[str, dict[str, str]] = {
    "EXT_SPONSORS": {
        "material": "EXT_SPONSORS_LIVERYLAB_NEUTRAL.material",
        "opacity": "EXT_SPONSORS_LIVERYLAB_NEUTRAL_OPACITY.texture",
    },
    "INT_DRIVERS": {
        "material": "INT_DRIVERS_LIVERYLAB_NEUTRAL.material",
        "opacity": "INT_DRIVERS_LIVERYLAB_NEUTRAL_OPACITY.texture",
    },
}
SF25_PREVIEW_HIDDEN_MATERIALS = ["EXT_SPONSORS", "INT_DRIVERS"]
F2004_CAR_MODEL = "ks_ferrari_f2004"
F2004_NEUTRAL_MATERIAL_SLOTS: dict[str, dict[str, str]] = {
    "EXT_SPONSORS": {
        "label": "EXT SPONSORS",
        "source": f"content\\cars\\{F2004_CAR_MODEL}\\materials\\EXT_SPONSORS.material",
        "material": "EXT_SPONSORS_LIVERYLAB_NEUTRAL.material",
        "opacity": "EXT_SPONSORS_LIVERYLAB_NEUTRAL_OPACITY.texture",
    },
    "EXT_DECALS": {
        "label": "EXT DECALS",
        "source": f"content\\cars\\{F2004_CAR_MODEL}\\materials\\EXT_DECALS.material",
        "material": "EXT_DECALS_LIVERYLAB_NEUTRAL.material",
        "opacity": "EXT_DECALS_LIVERYLAB_NEUTRAL_OPACITY.texture",
    },
    "EXT_DRIVERS": {
        "label": "EXT DRIVERS",
        "source": f"content\\cars\\{F2004_CAR_MODEL}\\materials\\EXT_DRIVERS.material",
        "material": "EXT_DRIVERS_LIVERYLAB_NEUTRAL.material",
        "opacity": "EXT_DRIVERS_LIVERYLAB_NEUTRAL_OPACITY.texture",
    },
    "EXT_CAM_FIN": {
        "label": "EXT CAM FIN",
        "source": f"content\\cars\\{F2004_CAR_MODEL}\\materials\\EXT_CAM_FIN.material",
        "opacity_source": f"content\\cars\\{F2004_CAR_MODEL}\\materials\\EXT_DECALS.material",
        "material": "EXT_CAM_FIN_LIVERYLAB_NEUTRAL.material",
        "opacity": "EXT_CAM_FIN_LIVERYLAB_NEUTRAL_OPACITY.texture",
    },
}
F2004_PREVIEW_HIDDEN_MATERIALS = ["EXT_SPONSORS", "EXT_DECALS", "EXT_DRIVERS"]


def _is_sf25_entry(entry: dict[str, Any]) -> bool:
    car_model = str(entry.get("car_model") or "").strip().lower()
    car_content = normalize_skin_asset_rel_path(str(entry.get("car_content_path") or "")).lower()
    return car_model == SF25_CAR_MODEL or car_content == f"content\\cars\\{SF25_CAR_MODEL}"


def _is_f2004_entry(entry: dict[str, Any]) -> bool:
    car_model = str(entry.get("car_model") or "").strip().lower()
    car_content = normalize_skin_asset_rel_path(str(entry.get("car_content_path") or "")).lower()
    return car_model == F2004_CAR_MODEL or car_content == f"content\\cars\\{F2004_CAR_MODEL}"


def _sf25_slot_key(slot_id: str | None) -> str:
    return str(slot_id or "").strip().upper().replace(" ", "_")


def _sf25_preview_hidden_materials(entry: dict[str, Any]) -> list[str]:
    return list(SF25_PREVIEW_HIDDEN_MATERIALS) if _is_sf25_entry(entry) else []


def _f2004_preview_hidden_materials(entry: dict[str, Any]) -> list[str]:
    return list(F2004_PREVIEW_HIDDEN_MATERIALS) if _is_f2004_entry(entry) else []


def _minimal_design_material_slot(slot_id: str, material_rel: str, label: str | None = None) -> bytes:
    clean_slot_id = str(slot_id or "").strip()
    clean_label = str(label or "").strip() or clean_slot_id.replace("_", " ")
    empty_payload = build_fields([
        ProtoField(1, 2, b""),
        ProtoField(2, 2, b""),
        ProtoField(3, 2, b""),
    ])
    return build_fields([
        ProtoField(1, 2, clean_slot_id.encode("utf-8")),
        ProtoField(2, 2, clean_label.encode("utf-8")),
        ProtoField(3, 2, norm_slashes(material_rel).encode("utf-8")),
        ProtoField(6, 2, empty_payload),
    ])


def _design_slot_id(slot_raw: bytes) -> str:
    try:
        for field in parse_fields(slot_raw):
            if field.number == 1 and field.wire == 2 and isinstance(field.value, (bytes, bytearray)):
                text = is_printable_utf8(bytes(field.value))
                if text:
                    return text
    except Exception:
        return ""
    return ""


def _design_slot_label(slot_raw: bytes) -> str:
    try:
        for field in parse_fields(slot_raw):
            if field.number == 2 and field.wire == 2 and isinstance(field.value, (bytes, bytearray)):
                text = is_printable_utf8(bytes(field.value))
                if text:
                    return text
    except Exception:
        return ""
    return ""


def _ensure_design_material_slot(data: bytes, slot_id: str, material_rel: str, label: str | None = None) -> tuple[bytes, bool]:
    slot_key = _sf25_slot_key(slot_id)
    fields = parse_fields(data)
    without_slot: list[ProtoField] = []
    existing_label = str(label or "").strip()
    for field in fields:
        if field.number == 3 and field.wire == 2 and isinstance(field.value, (bytes, bytearray)):
            slot_raw = bytes(field.value)
            if _sf25_slot_key(_design_slot_id(slot_raw)) == slot_key:
                existing_label = _design_slot_label(slot_raw) or existing_label
                continue
        without_slot.append(field)

    slot_field = ProtoField(3, 2, _minimal_design_material_slot(slot_key, material_rel, existing_label or label))
    last_skin_index = -1
    for index, field in enumerate(without_slot):
        if field.number == 3 and field.wire == 2 and isinstance(field.value, (bytes, bytearray)):
            if _sf25_slot_key(_design_slot_id(bytes(field.value))).startswith("EXT_SKIN"):
                last_skin_index = index

    out: list[ProtoField] = []
    inserted = False
    for index, field in enumerate(without_slot):
        out.append(field)
        if index == last_skin_index:
            out.append(slot_field)
            inserted = True
    if not inserted:
        out.append(slot_field)

    new_data = build_fields(out)
    return (new_data, True) if new_data != data else (data, False)


def _finalstate_snapshot_contains_text(snapshot_raw: bytes, needles: set[str]) -> bool:
    if not needles:
        return False
    for _path, text in collect_proto_strings(snapshot_raw):
        normalized = norm_slashes(text).lower()
        if normalized in needles:
            return True
    return False


def _minimal_material_snapshot(material_rel: str, label: str | None = None) -> bytes:
    clean_label = str(label or "").strip() or Path(*split_content_path(material_rel)).stem.replace("_", " ")
    return build_fields([
        ProtoField(1, 2, clean_label.encode("utf-8")),
        ProtoField(2, 2, build_fields([
            ProtoField(1, 2, norm_slashes(material_rel).encode("utf-8")),
        ])),
    ])


def _ensure_carfinalstate_material_snapshot(data: bytes, material_rel: str, label: str | None = None) -> tuple[bytes, bool]:
    material_rel = norm_slashes(material_rel)
    material_key = material_rel.lower()
    label_key = str(label or "").strip().lower()
    match_keys = {material_key}
    if label_key:
        match_keys.add(label_key)
    fields = parse_fields(data)
    out: list[ProtoField] = []
    changed = False

    for top in fields:
        if top.number != 10 or top.wire != 2 or not isinstance(top.value, (bytes, bytearray)):
            out.append(top)
            continue

        field10 = bytes(top.value)
        f10_fields = parse_fields(field10)
        remaining: list[ProtoField] = []
        had_snapshot = False
        for field in f10_fields:
            if field.number == 3 and field.wire == 2 and isinstance(field.value, (bytes, bytearray)):
                if _finalstate_snapshot_contains_text(bytes(field.value), match_keys):
                    had_snapshot = True
                    continue
            remaining.append(field)

        snapshot = ProtoField(3, 2, _minimal_material_snapshot(material_rel, label))
        new_f10: list[ProtoField] = []
        inserted = False
        for field in remaining:
            if not inserted and field.number == 3 and field.wire == 2:
                new_f10.append(snapshot)
                inserted = True
            new_f10.append(field)
        if not inserted:
            new_f10.append(snapshot)

        new_field10 = build_fields(new_f10)
        if had_snapshot or new_field10 != field10:
            out.append(ProtoField(top.number, top.wire, new_field10))
            changed = True
        else:
            out.append(top)

    return (build_fields(out), True) if changed else (data, False)


def _ensure_carstate_material_snapshots(data: bytes, materials: dict[str, str]) -> tuple[bytes, bool]:
    patched = data
    changed = False
    for material_rel, label in materials.items():
        next_data, did_change = _ensure_carfinalstate_material_snapshot(patched, material_rel, label)
        if did_change:
            patched = next_data
            changed = True
    if changed:
        return patched, True

    try:
        fields = parse_fields(data)
    except Exception:
        return data, False
    out: list[ProtoField] = []
    wrapped_changed = False
    for field in fields:
        if field.number == 2 and field.wire == 2 and isinstance(field.value, (bytes, bytearray)):
            try:
                nested, nested_changed = _ensure_carstate_material_snapshots(bytes(field.value), materials)
            except Exception:
                out.append(field)
                continue
            if nested_changed:
                out.append(ProtoField(field.number, field.wire, nested))
                wrapped_changed = True
                continue
        out.append(field)
    return (build_fields(out), True) if wrapped_changed else (data, False)


def _sf25_patch_material_ref(data: bytes, replacements: dict[str, str]) -> bytes:
    exact = {
        _normalize_exact_text(old): norm_slashes(new)
        for old, new in replacements.items()
        if old and new
    }
    patched = patch_strings_recursive_exact(data, exact)
    if patched != data:
        return patched

    changed = False
    fields: list[ProtoField] = []
    for field in parse_fields(data):
        if field.wire == 2 and isinstance(field.value, bytes):
            text = is_printable_utf8(field.value)
            if text is not None:
                new_text = text
                for old, new in replacements.items():
                    new_text = new_text.replace(norm_slashes(old), norm_slashes(new))
                if new_text != text:
                    fields.append(ProtoField(field.number, field.wire, new_text.encode("utf-8")))
                    changed = True
                    continue
            try:
                nested = _sf25_patch_material_ref(bytes(field.value), replacements)
            except Exception:
                nested = bytes(field.value)
            if nested != bytes(field.value):
                fields.append(ProtoField(field.number, field.wire, nested))
                changed = True
                continue
        fields.append(field)
    return build_fields(fields) if changed else data


def _sf25_zero_opacity_texturemips(texture_path: Path, reference_mips_path: Path) -> tuple[bytes, str, str]:
    return _solid_texturemips_from_rgba(
        texture_path,
        reference_mips_path,
        "Base_OpacityMap",
        (0, 0, 0, 0),
    )


def _ensure_sf25_neutral_sponsor_materials(entry: dict[str, Any], game_root: Path) -> dict[str, Any]:
    if not _is_sf25_entry(entry):
        return {"changed": False, "reason": "not Ferrari SF25", "copied": [], "patched": []}

    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    local_skin_folder = _local_skin_asset_path(entry, skin_rel)
    if not skin_rel or local_skin_folder is None:
        return {"changed": False, "reason": "local skin folder missing", "copied": [], "patched": []}

    slot_source_refs: dict[str, str] = {}
    for _kind, design_path in _skin_file_paths_for_kinds(entry, {"design"}):
        try:
            slot_materials = extract_design_slot_materials(design_path.read_bytes())
        except Exception:
            continue
        for slot_id, materials in slot_materials.items():
            slot_key = _sf25_slot_key(slot_id)
            if slot_key not in SF25_NEUTRAL_MATERIAL_SLOTS or slot_source_refs.get(slot_key):
                continue
            material_ref = normalize_skin_asset_rel_path(str((materials or [""])[0] or ""))
            if material_ref:
                slot_source_refs[slot_key] = material_ref

    if not slot_source_refs:
        return {"changed": False, "reason": "SF25 sponsor material slots missing", "copied": [], "patched": []}

    copied: list[str] = []
    patched: list[str] = []
    replacements: dict[str, str] = {}
    material_results: list[dict[str, Any]] = []
    local_skin_folder.mkdir(parents=True, exist_ok=True)

    for slot_key, source_ref in slot_source_refs.items():
        spec = SF25_NEUTRAL_MATERIAL_SLOTS[slot_key]
        target_material_rel = norm_slashes(f"{skin_rel}\\{spec['material']}")
        target_opacity_rel = norm_slashes(f"{skin_rel}\\{spec['opacity']}")
        target_material = _local_skin_asset_path(entry, target_material_rel)
        target_opacity = _local_skin_asset_path(entry, target_opacity_rel)
        if target_material is None or target_opacity is None:
            continue

        source_material = _local_skin_asset_path(entry, source_ref)
        if not source_material or not source_material.exists():
            source_material = _disk_from_game_rel(game_root, source_ref)
        if not source_material or not source_material.exists() or not source_material.is_file():
            material_results.append({"slot": slot_key, "status": "skipped", "reason": "source material missing", "source": source_ref})
            continue

        source_info = inspect_material(source_material, root=game_root)
        opacity_slot = next(
            (
                slot
                for slot in source_info.get("texture_slots", [])
                if str(slot.get("name") or "") == "Base_OpacityMap" and slot.get("value")
            ),
            None,
        )
        source_opacity_ref = normalize_skin_asset_rel_path(str(opacity_slot.get("value") or "")) if isinstance(opacity_slot, dict) else ""
        source_opacity = _local_skin_asset_path(entry, source_opacity_ref)
        if not source_opacity or not source_opacity.exists():
            source_opacity = _disk_from_game_rel(game_root, source_opacity_ref)
        if not source_opacity or not source_opacity.exists() or not source_opacity.is_file():
            material_results.append({"slot": slot_key, "status": "skipped", "reason": "source opacity texture missing", "source": source_opacity_ref})
            continue

        source_opacity_mips = source_opacity.with_suffix(".texturemips")
        if not source_opacity_mips.exists():
            material_results.append({"slot": slot_key, "status": "skipped", "reason": "source opacity texturemips missing", "source": str(source_opacity_mips)})
            continue

        if not target_opacity.exists():
            _copy_game_file(source_opacity, target_opacity, label="SF25 neutral opacity descriptor", backup=True)
            copied.append(str(target_opacity))
        localize_texture_descriptor_source(target_opacity, str(entry.get("skin_id") or ""), writer=None)
        target_opacity_mips = target_opacity.with_suffix(".texturemips")
        zero_mips, format_name, note = _sf25_zero_opacity_texturemips(target_opacity, source_opacity_mips)
        if not target_opacity_mips.exists() or target_opacity_mips.read_bytes() != zero_mips:
            _write_local_skin_bytes_with_backup(target_opacity_mips, zero_mips)
            patched.append(str(target_opacity_mips))

        source_material_data = source_material.read_bytes()
        neutral_material = _set_material_texture_slots_nested(source_material_data, {"Base_OpacityMap": target_opacity_rel})
        neutral_material = _set_material_float_parameters(neutral_material, {"Base_HasOpacityMap"}, 1.0)
        neutral_material = _ensure_material_override_flags(neutral_material, 21, {"Base_HasOpacityMap"}, 1)
        neutral_material = _ensure_material_override_flags(neutral_material, 22, {"Base_OpacityMap"}, 1)
        if not target_material.exists() or target_material.read_bytes() != neutral_material:
            _write_local_skin_bytes_with_backup(target_material, neutral_material)
            patched.append(str(target_material))

        replacements[norm_slashes(source_ref)] = target_material_rel
        material_results.append({
            "slot": slot_key,
            "status": "updated",
            "source": source_ref,
            "material": target_material_rel,
            "opacity": target_opacity_rel,
            "format": format_name,
            "note": note,
        })

    if replacements:
        for kind, path in _skin_file_paths_for_kinds(entry, {"design", "visual_preset", "carfinalstate", "carfinalstates"}):
            old_data = path.read_bytes()
            new_data = _sf25_patch_material_ref(old_data, replacements)
            if new_data == old_data:
                continue
            _write_local_skin_bytes_with_backup(path, new_data)
            patched.append(str(path))

    return {
        "changed": bool(copied or patched),
        "copied": copied,
        "patched": patched,
        "replacements": replacements,
        "materials": material_results,
    }


def _ensure_f2004_neutral_overlay_materials(entry: dict[str, Any], game_root: Path) -> dict[str, Any]:
    if not _is_f2004_entry(entry):
        return {"changed": False, "reason": "not Ferrari F2004", "copied": [], "patched": []}

    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    local_skin_folder = _local_skin_asset_path(entry, skin_rel)
    if not skin_rel or local_skin_folder is None:
        return {"changed": False, "reason": "local skin folder missing", "copied": [], "patched": []}

    copied: list[str] = []
    patched: list[str] = []
    replacements: dict[str, str] = {}
    material_results: list[dict[str, Any]] = []
    local_skin_folder.mkdir(parents=True, exist_ok=True)

    slot_source_refs: dict[str, str] = {}
    for _kind, design_path in _skin_file_paths_for_kinds(entry, {"design"}):
        try:
            slot_materials = extract_design_slot_materials(design_path.read_bytes())
        except Exception:
            continue
        for slot_id, materials in slot_materials.items():
            slot_key = _sf25_slot_key(slot_id)
            if slot_key not in F2004_NEUTRAL_MATERIAL_SLOTS or slot_source_refs.get(slot_key):
                continue
            material_ref = normalize_skin_asset_rel_path(str((materials or [""])[0] or ""))
            if material_ref:
                slot_source_refs[slot_key] = material_ref

    for slot_key, spec in F2004_NEUTRAL_MATERIAL_SLOTS.items():
        source_ref = slot_source_refs.get(slot_key) or normalize_skin_asset_rel_path(str(spec.get("source") or ""))
        target_material_rel = norm_slashes(f"{skin_rel}\\{spec['material']}")
        target_opacity_rel = norm_slashes(f"{skin_rel}\\{spec['opacity']}") if spec.get("opacity") else ""
        target_material = _local_skin_asset_path(entry, target_material_rel)
        target_opacity = _local_skin_asset_path(entry, target_opacity_rel) if target_opacity_rel else None
        if target_material is None:
            continue

        source_material = _local_skin_asset_path(entry, source_ref)
        if not source_material or not source_material.exists():
            source_material = _disk_from_game_rel(game_root, source_ref)
        if not source_material or not source_material.exists() or not source_material.is_file():
            material_results.append({"slot": slot_key, "status": "skipped", "reason": "source material missing", "source": source_ref})
            continue

        if target_opacity is None:
            material_results.append({"slot": slot_key, "status": "skipped", "reason": "target opacity missing", "source": source_ref})
            continue

        source_info = inspect_material(source_material, root=game_root)
        opacity_slot = next(
            (
                slot
                for slot in source_info.get("texture_slots", [])
                if str(slot.get("name") or "") == "Base_OpacityMap" and slot.get("value")
            ),
            None,
        )
        opacity_source_ref = source_ref
        if not opacity_slot and spec.get("opacity_source"):
            opacity_source_ref = normalize_skin_asset_rel_path(str(spec.get("opacity_source") or ""))
            opacity_source_material = _disk_from_game_rel(game_root, opacity_source_ref)
            if opacity_source_material and opacity_source_material.exists() and opacity_source_material.is_file():
                opacity_source_info = inspect_material(opacity_source_material, root=game_root)
                opacity_slot = next(
                    (
                        slot
                        for slot in opacity_source_info.get("texture_slots", [])
                        if str(slot.get("name") or "") == "Base_OpacityMap" and slot.get("value")
                    ),
                    None,
                )
        source_opacity_ref = normalize_skin_asset_rel_path(str(opacity_slot.get("value") or "")) if isinstance(opacity_slot, dict) else ""
        source_opacity = _disk_from_game_rel(game_root, source_opacity_ref)
        if not source_opacity or not source_opacity.exists() or not source_opacity.is_file():
            material_results.append({"slot": slot_key, "status": "skipped", "reason": "source opacity texture missing", "source": source_opacity_ref, "opacitySource": opacity_source_ref})
            continue

        source_opacity_mips = source_opacity.with_suffix(".texturemips")
        if not source_opacity_mips.exists():
            material_results.append({"slot": slot_key, "status": "skipped", "reason": "source opacity texturemips missing", "source": str(source_opacity_mips)})
            continue

        if not target_opacity.exists():
            _copy_game_file(source_opacity, target_opacity, label="F2004 neutral opacity descriptor", backup=True)
            copied.append(str(target_opacity))
        localize_texture_descriptor_source(target_opacity, str(entry.get("skin_id") or ""), writer=None)
        target_opacity_mips = target_opacity.with_suffix(".texturemips")
        zero_mips, format_name, note = _sf25_zero_opacity_texturemips(target_opacity, source_opacity_mips)
        if not target_opacity_mips.exists() or target_opacity_mips.read_bytes() != zero_mips:
            _write_local_skin_bytes_with_backup(target_opacity_mips, zero_mips)
            patched.append(str(target_opacity_mips))

        source_material_data = source_material.read_bytes()
        neutral_material = _set_material_texture_slots_nested(source_material_data, {"Base_OpacityMap": target_opacity_rel})
        neutral_material = _set_material_float_parameters(neutral_material, {"Base_HasOpacityMap"}, 1.0)
        neutral_material = _ensure_material_override_flags(neutral_material, 21, {"Base_HasOpacityMap"}, 1)
        neutral_material = _ensure_material_override_flags(neutral_material, 22, {"Base_OpacityMap"}, 1)
        if not target_material.exists() or target_material.read_bytes() != neutral_material:
            _write_local_skin_bytes_with_backup(target_material, neutral_material)
            patched.append(str(target_material))

        replacements[source_ref] = target_material_rel
        material_results.append({
            "slot": slot_key,
            "status": "updated",
            "source": source_ref,
            "opacitySource": opacity_source_ref,
            "material": target_material_rel,
            "opacity": target_opacity_rel,
            "format": format_name,
            "note": note,
        })

    if replacements:
        snapshot_materials = {
            target_material_rel: str(F2004_NEUTRAL_MATERIAL_SLOTS.get(_sf25_slot_key(Path(source_ref).stem), {}).get("label") or _sf25_slot_key(Path(source_ref).stem))
            for source_ref, target_material_rel in replacements.items()
        }
        for kind, path in _skin_file_paths_for_kinds(entry, {"design", "visual_preset", "carfinalstate", "carfinalstates"}):
            old_data = path.read_bytes()
            new_data = _sf25_patch_material_ref(old_data, replacements)
            if kind == "design":
                for slot_key, target_material_rel in replacements.items():
                    slot_id = _sf25_slot_key(Path(slot_key).stem)
                    if slot_id in F2004_NEUTRAL_MATERIAL_SLOTS:
                        new_data, _slot_changed = _ensure_design_material_slot(
                            new_data,
                            slot_id,
                            target_material_rel,
                            F2004_NEUTRAL_MATERIAL_SLOTS[slot_id].get("label"),
                        )
            elif kind in {"carfinalstate", "carfinalstates"}:
                new_data, _snapshot_changed = _ensure_carstate_material_snapshots(new_data, snapshot_materials)
            if new_data == old_data:
                continue
            _write_local_skin_bytes_with_backup(path, new_data)
            patched.append(str(path))

    return {
        "changed": bool(copied or patched),
        "copied": copied,
        "patched": patched,
        "replacements": replacements,
        "materials": material_results,
    }


def _ensure_entry_ext_skin_materials_are_local(entry: dict[str, Any], game_root: Path) -> dict[str, Any]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    local_skin_folder = _local_skin_asset_path(entry, skin_rel)
    if not skin_rel or local_skin_folder is None:
        return {"changed": False, "copied": [], "patched": [], "replacements": {}, "reason": "local skin folder missing"}

    replacements: dict[str, str] = {}
    copied: list[str] = []
    patched: list[str] = []
    for _kind, design_path in _skin_file_paths_for_kinds(entry, {"design"}):
        try:
            slot_materials = extract_design_slot_materials(design_path.read_bytes())
        except Exception:
            continue
        for slot_id, materials in slot_materials.items():
            if not _is_ext_skin_design_slot(slot_id):
                continue
            for material_ref in materials:
                material_norm = normalize_skin_asset_rel_path(str(material_ref or ""))
                if not material_norm:
                    continue
                if material_norm.lower().startswith(skin_rel.lower() + "\\"):
                    continue
                source_material = _disk_from_game_rel(game_root, material_norm)
                if not source_material or not source_material.exists() or not source_material.is_file():
                    continue
                target_rel = norm_slashes(f"{skin_rel}\\{source_material.name}")
                target_path = _local_skin_asset_path(entry, target_rel)
                if target_path is None:
                    continue
                if not target_path.exists():
                    _copy_game_file(source_material, target_path, label="skin-local EXT_SKIN material")
                    copied.append(str(target_path))
                replacements[norm_slashes(material_norm)] = norm_slashes(target_rel)

    if not replacements:
        return {"changed": False, "copied": copied, "patched": patched, "replacements": replacements}

    exact_replacements = {_normalize_exact_text(old): new for old, new in replacements.items()}
    for target_rel in sorted(set(replacements.values())):
        material_path = _local_skin_asset_path(entry, target_rel)
        if not material_path or not material_path.exists():
            continue
        original = material_path.read_bytes()
        patched_material = patch_strings_recursive_exact(original, exact_replacements)
        if patched_material != original:
            _write_local_skin_bytes_with_backup(material_path, patched_material)
            patched.append(str(material_path))

    for _kind, path in _skin_file_paths_for_kinds(entry, {"design", "visual_preset", "carfinalstate", "carfinalstates"}):
        original = path.read_bytes()
        updated = patch_strings_recursive_exact(original, exact_replacements)
        if updated == original:
            continue
        _write_local_skin_bytes_with_backup(path, updated)
        patched.append(str(path))

    return {"changed": bool(copied or patched), "copied": copied, "patched": patched, "replacements": replacements}


def _activate_livery_material_map_overrides(material_data: bytes) -> bytes:
    patched = _set_material_float_parameters(material_data, set(paint_only_livery.MATERIAL_MAP_FLAG_PARAMS), 1.0)
    patched = _ensure_material_override_flags(patched, 21, paint_only_livery.MATERIAL_MAP_FLAG_PARAMS, 1)
    patched = _ensure_material_override_flags(patched, 22, paint_only_livery.MATERIAL_MAP_TEXTURE_SLOTS, 1)
    return patched


def _activate_livery_material_texture_overrides(material_data: bytes, slot_replacements: dict[str, str]) -> bytes:
    patched = _activate_livery_material_map_overrides(material_data)
    if slot_replacements.get("Base_BaseColorMap"):
        patched = _set_material_float_parameters(patched, {"Base_HasBaseColorMap"}, 1.0)
        patched = _ensure_material_override_flags(patched, 21, {"Base_HasBaseColorMap"}, 1)
    if slot_replacements:
        patched = _ensure_material_override_flags(patched, 22, set(slot_replacements), 1)
    return patched


def _livery_material_texture_slot_replacements(
    entry: dict[str, Any],
    texture_paths: dict[str, Path],
    texture_keys: set[str] | None = None,
) -> dict[str, str]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_folder_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    local_root = _entry_local_root(entry)
    replacements: dict[str, str] = {}
    wanted = set(texture_keys) if texture_keys is not None else set(paint_only_livery.TEXTURE_FILENAMES)
    for slot_name, texture_key in paint_only_livery.SLOT_TO_TEXTURE_KEY.items():
        if texture_key not in wanted:
            continue
        texture = texture_paths.get(texture_key)
        if texture is None:
            continue
        rel = ""
        if local_root is not None:
            try:
                rel = norm_slashes(str(texture.resolve().relative_to(local_root.resolve())))
            except Exception:
                rel = ""
        if not rel and skin_folder_rel:
            rel = norm_slashes(f"{skin_folder_rel}\\{texture.name}")
        if rel:
            replacements[slot_name] = rel
    return replacements


def _livery_material_finish_texture_slot_replacements(entry: dict[str, Any], texture_paths: dict[str, Path]) -> dict[str, str]:
    return _livery_material_texture_slot_replacements(entry, texture_paths, set(LIVERY_MATERIAL_FINISH_TEXTURE_KEYS.values()))


def _livery_material_texture_paths(
    entry: dict[str, Any],
    material_path: Path,
    texture_keys: set[str] | None = None,
) -> dict[str, Path]:
    local_root = _entry_local_root(entry)
    root = local_root or material_path.parent
    paths: dict[str, Path] = {}
    wanted = set(texture_keys) if texture_keys is not None else set(paint_only_livery.TEXTURE_FILENAMES)
    try:
        material = inspect_material(material_path, root=root)
    except Exception:
        material = {}
    for slot in material.get("texture_slots", []):
        slot_name = str(slot.get("name") or "")
        texture_key = paint_only_livery.SLOT_TO_TEXTURE_KEY.get(slot_name)
        if texture_key not in wanted:
            continue
        resolved_text = str(slot.get("resolved_path") or "").strip()
        resolved = Path(resolved_text) if resolved_text else material_resolve_ref(root, str(slot.get("value") or ""))
        if resolved and resolved.exists() and resolved.suffix.lower() == ".texture":
            paths[texture_key] = resolved

    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_folder = _local_skin_asset_path(entry, str(files.get("skin_folder") or ""))
    if skin_folder and skin_folder.exists():
        for texture_key in wanted:
            filename = paint_only_livery.TEXTURE_FILENAMES.get(texture_key)
            if not filename:
                continue
            direct = skin_folder / filename
            candidate = direct if direct.exists() else _find_texture_by_stem(skin_folder, Path(filename).stem)
            if candidate and candidate.exists() and candidate.suffix.lower() == ".texture":
                if texture_key in paths and _is_path_within(paths[texture_key], skin_folder):
                    continue
                paths[texture_key] = candidate
    return paths


def _livery_material_finish_texture_paths(entry: dict[str, Any], material_path: Path) -> dict[str, Path]:
    return _livery_material_texture_paths(entry, material_path, set(LIVERY_MATERIAL_FINISH_TEXTURE_KEYS.values()))


def _ensure_livery_material_texture_bindings(entry: dict[str, Any], material_path: Path, texture_paths: dict[str, Path]) -> dict[str, Any]:
    if not material_path or not material_path.exists():
        return {"updated": False, "path": str(material_path or ""), "reason": "material missing"}
    replacements = _livery_material_texture_slot_replacements(entry, texture_paths)
    if not replacements:
        return {"updated": False, "path": str(material_path), "reason": "livery textures missing"}
    original = material_path.read_bytes()
    patched = _activate_livery_material_texture_overrides(original, replacements)
    patched = _set_material_texture_slots_nested(patched, replacements)
    if patched == original:
        return {"updated": False, "path": str(material_path), "reason": "current", "slots": sorted(replacements)}
    backup = _write_game_bytes(material_path, patched, label="skin-local livery texture bindings")
    return {
        "updated": True,
        "path": str(material_path),
        "backup": str(backup) if backup else "",
        "slots": sorted(replacements),
    }


def _ensure_livery_material_map_bindings(entry: dict[str, Any], material_path: Path, texture_paths: dict[str, Path]) -> dict[str, Any]:
    if not material_path or not material_path.exists():
        return {"updated": False, "path": str(material_path or ""), "reason": "material missing"}
    replacements = _livery_material_finish_texture_slot_replacements(entry, texture_paths)
    if not replacements:
        return {"updated": False, "path": str(material_path), "reason": "material map textures missing"}
    original = material_path.read_bytes()
    patched = _activate_livery_material_map_overrides(original)
    patched = _set_material_texture_slots_nested(patched, replacements)
    if patched == original:
        return {"updated": False, "path": str(material_path), "reason": "current", "slots": sorted(replacements)}
    backup = _write_game_bytes(material_path, patched, label="skin-local material map bindings")
    return {
        "updated": True,
        "path": str(material_path),
        "backup": str(backup) if backup else "",
        "slots": sorted(replacements),
    }


def _livery_material_finish_value_for_texture_key(texture_key: str, finish: dict[str, Any] | None = None) -> int:
    finish_by_texture_key = {texture: key for key, texture in LIVERY_MATERIAL_FINISH_TEXTURE_KEYS.items()}
    finish_key = finish_by_texture_key.get(str(texture_key or ""))
    if not finish_key:
        return 0
    values = _normalize_livery_material_finish(finish if isinstance(finish, dict) else {})
    return int(round(_finish_clamped_float(values.get(finish_key), LIVERY_MATERIAL_FINISH_DEFAULTS[finish_key]) * 255.0))


def _livery_material_map_source_candidates(entry: dict[str, Any], game_root: Path, skin_rel: str, texture_key: str) -> list[Path]:
    filename = paint_only_livery.TEXTURE_FILENAMES.get(texture_key, "")
    stem = Path(filename).stem
    candidates: list[Path] = []

    def add(candidate: Path | None) -> None:
        if candidate and candidate.exists() and candidate.suffix.lower() == ".texture":
            resolved = candidate.resolve()
            if resolved not in {row.resolve() for row in candidates}:
                candidates.append(candidate)

    game_skin_folder = _disk_from_game_rel(game_root, skin_rel)
    if game_skin_folder and game_skin_folder.exists():
        add(_find_texture_by_stem(game_skin_folder, stem))

    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    source_skin_rel = normalize_skin_asset_rel_path(str(source_skin.get("skin_folder") or ""))
    source_skin_folder = _disk_from_game_rel(game_root, source_skin_rel)
    if source_skin_folder and source_skin_folder.exists():
        add(_find_texture_by_stem(source_skin_folder, stem))

    source_material_rel = normalize_skin_asset_rel_path(str(source_skin.get("material") or ""))
    source_material = _disk_from_game_rel(game_root, source_material_rel)
    if source_material and source_material.exists():
        add(_find_texture_by_stem(source_material.parent, stem))

    try:
        add(_paint_only_template_texture(game_root, "", texture_key))
    except Exception:
        pass
    return candidates


def _livery_material_slot_source_texture(
    root: Path,
    material_path: Path,
    slot_name: str,
) -> Path | None:
    if not material_path.exists():
        return None
    try:
        material = inspect_material(material_path, root=root)
    except Exception:
        return None
    for slot in material.get("texture_slots", []):
        if str(slot.get("name") or "") != slot_name:
            continue
        value = str(slot.get("value") or "").strip()
        if ".texture" not in value.lower():
            continue
        resolved_text = str(slot.get("resolved_path") or "").strip()
        resolved = Path(resolved_text) if resolved_text else material_resolve_ref(root, value)
        if resolved and resolved.exists() and resolved.suffix.lower() == ".texture":
            return resolved
    return None


def _ensure_livery_base_color_texture_in_local_skin(
    entry: dict[str, Any],
    game_root: Path,
    local_skin_folder: Path,
    skin_rel: str,
    material_paths: list[Path],
) -> dict[str, Any]:
    filename = paint_only_livery.TEXTURE_FILENAMES["base"]
    local_texture = _find_texture_by_stem(local_skin_folder, Path(filename).stem) or (local_skin_folder / filename)
    local_mips = local_texture.with_suffix(".texturemips")
    item: dict[str, Any] = {"textureKey": "base", "texture": str(local_texture), "status": "pending"}
    if local_texture.exists() and local_mips.exists():
        item.update({"status": "current", "texturemips": str(local_mips)})
        return item

    source_texture: Path | None = None
    source_mips: Path | None = None
    for material_path in material_paths:
        root = _entry_local_root(entry) if _entry_local_root(entry) and _is_path_within(material_path, _entry_local_root(entry)) else game_root
        candidate = _livery_material_slot_source_texture(root, material_path, "Base_BaseColorMap")
        if candidate and candidate.with_suffix(".texturemips").exists():
            source_texture = candidate
            source_mips = candidate.with_suffix(".texturemips")
            break
    if source_texture is None or source_mips is None:
        sources = [
            source
            for source in _livery_material_map_source_candidates(entry, game_root, skin_rel, "base")
            if source.with_suffix(".texturemips").exists()
        ]
        if sources:
            source_texture = sources[0]
            source_mips = source_texture.with_suffix(".texturemips")
    if source_texture is None or source_mips is None:
        item.update({"status": "missing_source", "reason": "base color source missing"})
        return item

    local_skin_folder.mkdir(parents=True, exist_ok=True)
    descriptor_source = ""
    if not local_texture.exists():
        _copy_game_file(source_texture, local_texture, label="skin-local base color texture descriptor")
        localize_texture_descriptor_source(local_texture, str(entry.get("skin_id") or local_skin_folder.name))
        descriptor_source = str(source_texture)
    try:
        neutral_mips, format_name, note = _neutral_ext_skin_texturemips_for_descriptor(
            local_texture,
            source_mips,
            "white",
            "Base_BaseColorMap",
        )
    except Exception:
        _copy_game_file(source_mips, local_mips, label="skin-local base color texturemips seed")
        item.update({
            "status": "copied",
            "source": str(source_texture),
            "descriptorSource": descriptor_source,
            "texture": str(local_texture),
            "texturemips": str(local_mips),
            "reason": "neutral base generation failed; copied source mips",
        })
        return item
    if local_mips.exists() and local_mips.read_bytes() == neutral_mips:
        item.update({
            "status": "current",
            "source": str(source_texture),
            "descriptorSource": descriptor_source,
            "texture": str(local_texture),
            "texturemips": str(local_mips),
        })
        if format_name:
            item.update({"format": format_name, "note": note})
        return item
    backup = _write_game_bytes(local_mips, neutral_mips, label="skin-local neutral base color texturemips")
    item.update({
        "status": "generated",
        "source": str(source_texture),
        "descriptorSource": descriptor_source,
        "texture": str(local_texture),
        "texturemips": str(local_mips),
        "backup": str(backup) if backup else "",
        "bytes": len(neutral_mips),
    })
    if format_name:
        item.update({"format": format_name, "note": note})
    return item


def _ensure_livery_material_map_pair(
    entry: dict[str, Any],
    game_root: Path,
    local_skin_folder: Path,
    skin_rel: str,
    texture_key: str,
    finish: dict[str, Any] | None = None,
    overwrite_existing: bool = False,
    neutral_values: dict[str, int] | None = None,
) -> dict[str, Any]:
    filename = paint_only_livery.TEXTURE_FILENAMES.get(texture_key, "")
    stem = Path(filename).stem
    local_texture = _find_texture_by_stem(local_skin_folder, stem) or (local_skin_folder / filename)
    local_mips = local_texture.with_suffix(".texturemips")
    item: dict[str, Any] = {"textureKey": texture_key, "texture": str(local_texture), "status": "pending"}
    if local_texture.exists() and local_mips.exists() and not overwrite_existing:
        item.update({"status": "current"})
        return item

    source_texture = local_texture if local_texture.exists() and local_mips.exists() else None
    source_mips = local_mips if source_texture is not None else None
    if source_texture is None or source_mips is None:
        sources = [
            source
            for source in _livery_material_map_source_candidates(entry, game_root, skin_rel, texture_key)
            if source.with_suffix(".texturemips").exists()
        ]
        if not sources:
            item.update({"status": "missing_source", "reason": "material map source missing"})
            return item
        source_texture = sources[0]
        source_mips = source_texture.with_suffix(".texturemips")

    if not local_texture.exists():
        local_texture = local_skin_folder / source_texture.name
        local_mips = local_texture.with_suffix(".texturemips")
    local_texture.parent.mkdir(parents=True, exist_ok=True)
    descriptor_source = ""
    if not local_texture.exists():
        _copy_game_file(source_texture, local_texture, label="skin-local material map texture descriptor")
        localize_texture_descriptor_source(local_texture, str(entry.get("skin_id") or local_skin_folder.name))
        descriptor_source = str(source_texture)

    byte_value = (
        max(0, min(255, int(neutral_values[texture_key])))
        if isinstance(neutral_values, dict) and texture_key in neutral_values
        else _livery_material_finish_value_for_texture_key(texture_key, finish)
    )
    try:
        solid_mips = _solid_bc4_texturemips_for_descriptor_value(local_texture, source_mips, byte_value)
    except Exception:
        if descriptor_source or source_texture.resolve() == local_texture.resolve():
            raise
        _copy_game_file(source_texture, local_texture, label="skin-local material map texture descriptor")
        localize_texture_descriptor_source(local_texture, str(entry.get("skin_id") or local_skin_folder.name))
        descriptor_source = str(source_texture)
        solid_mips = _solid_bc4_texturemips_for_descriptor_value(local_texture, source_mips, byte_value)
    if local_mips.exists() and local_mips.read_bytes() == solid_mips:
        item.update({
            "status": "current",
            "source": str(source_texture),
            "descriptorSource": descriptor_source,
            "texture": str(local_texture),
            "texturemips": str(local_mips),
            "value": byte_value,
        })
        return item
    backup = _write_game_bytes(local_mips, solid_mips, label="skin-local material map texturemips")
    item.update({
        "status": "generated",
        "source": str(source_texture),
        "descriptorSource": descriptor_source,
        "texture": str(local_texture),
        "texturemips": str(local_mips),
        "backup": str(backup) if backup else "",
        "value": byte_value,
    })
    return item


def _ensure_livery_material_maps_in_local_skin(
    entry: dict[str, Any],
    game_root: Path,
    finish: dict[str, Any] | None = None,
    overwrite_existing: bool = False,
    neutral_values: dict[str, int] | None = None,
) -> dict[str, Any]:
    local_root = _entry_local_root(entry)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    local_skin_folder = _local_skin_asset_path(entry, skin_rel)
    items: list[dict[str, Any]] = []
    copied = 0
    generated = 0
    skipped = 0
    missing = 0
    if local_root is None or not skin_rel or local_skin_folder is None:
        return {"copied": 0, "skipped": 0, "missing": 0, "items": [], "reason": "local skin folder missing"}
    local_skin_folder.mkdir(parents=True, exist_ok=True)
    material_paths = _local_livery_material_paths(entry)
    material_path = material_paths[0] if material_paths else _local_livery_material_path(entry)
    try:
        item = _ensure_livery_base_color_texture_in_local_skin(
            entry,
            game_root,
            local_skin_folder,
            skin_rel,
            material_paths or [material_path],
        )
    except Exception as exc:
        item = {"textureKey": "base", "status": "skipped", "reason": str(exc)}
    if item.get("status") == "current":
        skipped += 1
    elif item.get("status") == "generated":
        generated += 1
    elif item.get("status") == "copied":
        copied += 1
    elif str(item.get("status") or "").startswith("missing"):
        missing += 1
    else:
        skipped += 1
    items.append(item)
    for texture_key in LIVERY_MATERIAL_FINISH_TEXTURE_KEYS.values():
        try:
            item = _ensure_livery_material_map_pair(
                entry,
                game_root,
                local_skin_folder,
                skin_rel,
                texture_key,
                finish=finish,
                overwrite_existing=overwrite_existing,
                neutral_values=neutral_values,
            )
        except Exception as exc:
            item = {"textureKey": texture_key, "status": "skipped", "reason": str(exc)}
        if item.get("status") == "current":
            skipped += 1
        elif item.get("status") == "generated":
            generated += 1
        elif item.get("status") == "copied":
            copied += 1
        elif str(item.get("status") or "").startswith("missing"):
            missing += 1
        else:
            skipped += 1
        items.append(item)
    texture_paths = _livery_material_texture_paths(entry, material_path) if material_path.exists() else {}
    binding_updates = [
        _ensure_livery_material_texture_bindings(entry, row, texture_paths)
        for row in material_paths
    ] if texture_paths else []
    binding_update = (
        binding_updates[0]
        if binding_updates
        else {"updated": False, "path": str(material_path), "reason": "material map textures missing"}
    )
    return {
        "copied": copied,
        "generated": generated,
        "skipped": skipped,
        "missing": missing,
        "items": items,
        "materialBinding": binding_update,
        "materialBindings": binding_updates,
    }


def _write_livery_material_finish_project_pngs(project: dict[str, Any], texture_paths: dict[str, Path]) -> dict[str, Any]:
    project_folder_text = str(project.get("projectFolder") or "").strip()
    if not project_folder_text:
        return {"updated": 0, "skipped": 0, "missing": 0, "items": [], "reason": "project folder missing"}
    project_folder = Path(project_folder_text)
    items: list[dict[str, Any]] = []
    updated = 0
    skipped = 0
    missing = 0
    for texture_key in LIVERY_MATERIAL_FINISH_TEXTURE_KEYS.values():
        texture = texture_paths.get(texture_key)
        meta_key = Path(paint_only_livery.TEXTURE_FILENAMES.get(texture_key, "")).stem.lower()
        if meta_key not in EXT_SKIN_PROJECT_TEXTURES:
            meta_key = ""
        if not meta_key:
            meta_key = Path(paint_only_livery.TEXTURE_FILENAMES.get(texture_key, "")).stem.lower()
        meta = _ext_skin_project_texture_meta(meta_key)
        target = project_folder / meta["png"]
        item: dict[str, Any] = {"textureKey": texture_key, "target": str(target), "texture": str(texture or ""), "status": "pending"}
        if texture is None or not texture.exists():
            item.update({"status": "missing_texture", "reason": "material map texture not found"})
            missing += 1
            items.append(item)
            continue
        if not texture.with_suffix(".texturemips").exists():
            item.update({"status": "missing_mips", "reason": "material map texturemips not found"})
            missing += 1
            items.append(item)
            continue
        try:
            converted = convert_texture_to_png(texture, target, overwrite=True, create_dds=False)
            item.update({"status": "updated" if not converted.get("skipped") else "current", "reason": converted.get("reason") or "", "path": converted.get("path") or str(target)})
            if converted.get("skipped"):
                skipped += 1
            else:
                updated += 1
        except Exception as exc:
            item.update({"status": "skipped", "reason": str(exc)})
            skipped += 1
        items.append(item)
    return {"updated": updated, "skipped": skipped, "missing": missing, "items": items}


def _write_livery_material_finish_maps(entry: dict[str, Any], material_path: Path, finish: dict[str, Any], texture_paths: dict[str, Path] | None = None) -> dict[str, Any]:
    texture_paths = texture_paths or _livery_material_finish_texture_paths(entry, material_path)
    items: list[dict[str, Any]] = []
    updated = 0
    skipped = 0
    missing = 0
    for finish_key, texture_key in LIVERY_MATERIAL_FINISH_TEXTURE_KEYS.items():
        texture = texture_paths.get(texture_key)
        item: dict[str, Any] = {
            "finishKey": finish_key,
            "textureKey": texture_key,
            "texture": str(texture or ""),
            "status": "pending",
        }
        if texture is None or not texture.exists():
            item.update({"status": "missing_texture", "reason": "material map texture not found"})
            missing += 1
            items.append(item)
            continue
        mips = texture.with_suffix(".texturemips")
        if not mips.exists():
            item.update({"status": "missing_mips", "reason": "material map texturemips not found"})
            missing += 1
            items.append(item)
            continue
        byte_value = int(round(_finish_clamped_float(finish.get(finish_key), LIVERY_MATERIAL_FINISH_DEFAULTS[finish_key]) * 255.0))
        item["value"] = byte_value
        try:
            texturemips_data = _solid_bc4_texturemips_for_descriptor_value(texture, mips, byte_value)
        except Exception as exc:
            item.update({"status": "skipped", "reason": str(exc)})
            skipped += 1
            items.append(item)
            continue
        old_data = mips.read_bytes()
        if old_data == texturemips_data:
            item.update({"status": "current", "texturemips": str(mips)})
            skipped += 1
            items.append(item)
            continue
        backup = _write_game_bytes(mips, texturemips_data, label=f"skin-local {texture_key} material finish")
        item.update({
            "status": "updated",
            "texturemips": str(mips),
            "backup": str(backup) if backup else "",
            "bytes": len(texturemips_data),
        })
        updated += 1
        items.append(item)
    return {"updated": updated, "skipped": skipped, "missing": missing, "items": items}


def _save_livery_project_material_finish(project: dict[str, Any], finish: dict[str, Any]) -> Path | None:
    project_path_text = str(project.get("projectPath") or "").strip()
    if not project_path_text:
        project_folder_text = str(project.get("projectFolder") or "").strip()
        if project_folder_text:
            project_path_text = str(Path(project_folder_text) / preview3d.PROJECT_FILE_NAME)
    if not project_path_text:
        return None
    project_path = Path(project_path_text)
    if not project_path.exists() and not project_path.parent.exists():
        return None
    data: dict[str, Any] = {}
    if project_path.exists():
        try:
            loaded = json.loads(project_path.read_text(encoding="utf-8"))
            data = loaded if isinstance(loaded, dict) else {}
        except Exception:
            data = {}
    data["materialFinish"] = _json_ready(finish)
    data["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    project_path.parent.mkdir(parents=True, exist_ok=True)
    project_path.write_text(json.dumps(_json_ready(data), indent=2, ensure_ascii=False), encoding="utf-8")
    return project_path


def _save_official_livery_material_finish(
    entry: dict[str, Any],
    skins: list[dict[str, Any]],
    index: int,
    project: dict[str, Any],
    finish: dict[str, Any],
) -> dict[str, Any]:
    project_folder = _entry_local_root(entry)
    if project_folder is None or not project_folder.exists() or not project_folder.is_dir():
        raise FileNotFoundError(f"Official project folder not found: {project_folder or ''}")

    previous_finish = _normalize_livery_material_finish(
        entry.get("material_finish") if isinstance(entry.get("material_finish"), dict) else {}
    )
    map_update = official_external_project_assets.write_material_finish_maps(project_folder, finish)
    project_payload = {**project, "projectFolder": str(project_folder)}
    project_path = _save_livery_project_material_finish(project_payload, finish)
    external_refresh = official_external_project_assets.refresh_external_livery_json(
        project_folder,
        car_guid=str(entry.get("car_guid") or ""),
        material_batch=_official_external_entry_material_batch(entry),
    )
    manifest_update = _update_official_external_project_manifest(
        project_folder,
        external_refresh.get("assetStatus") if isinstance(external_refresh.get("assetStatus"), dict) else {},
    )
    changed = previous_finish != finish or map_update.get("changed") is True
    game_update_required = changed and bool(entry.get("active"))
    entry["material_finish"] = _json_ready(finish)
    if game_update_required:
        _mark_my_skin_game_sync_required(entry, "Material finish changed", "material_finish")
    skins[index] = entry
    settings_path = save_my_skins(skins)
    return {
        "changed": changed,
        "gameUpdateRequired": game_update_required,
        "materialPath": "",
        "materialPaths": [],
        "materials": [],
        "projectPath": str(project_path) if project_path else "",
        "localRoot": str(project_folder),
        "materialFinish": finish,
        "materialMapUpdate": map_update,
        "projectMaterialMapUpdate": map_update,
        "gameSync": entry.get("game_sync") or {},
        "settingsPath": settings_path,
        "officialExternalRefresh": external_refresh,
        "officialManifest": manifest_update,
        "item": entry,
    }


def save_livery_material_finish(payload: dict[str, Any]) -> dict[str, Any]:
    data = payload if isinstance(payload, dict) else {}
    skin_key = str(data.get("skinKey") or "").strip()
    entry, skins, index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canEditMaterialFinish")
    finish = _normalize_livery_material_finish(data.get("materialFinish") if isinstance(data.get("materialFinish"), dict) else {})
    previous_finish = _normalize_livery_material_finish(
        entry.get("material_finish") if isinstance(entry.get("material_finish"), dict) else {}
    )
    if (
        project_types.project_type_for_entry(entry) == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY
        and official_external_workflow.role_for_entry(entry) == official_external_workflow.ROLE_PROJECT
    ):
        return _save_official_livery_material_finish(entry, skins, index, data, finish)
    local_root = _entry_local_root(entry)
    game_root = normalize_game_root(str(entry.get("game_root") or data.get("gameRoot") or get_default_root_folder()))
    local_ext_skin_sync = _ensure_entry_ext_skin_materials_are_local(entry, game_root)
    weissach_sticker_sync = _ensure_gt3rs_weissach_stickers_removed(entry)
    weissach_carbon_sync = _ensure_gt3rs_weissach_carbon_ext_skin_neutralized(entry)
    cayman_weissach_carbon_sync = _ensure_cayman_gt4rs_weissach_carbon_ext_skin_neutralized(entry)
    material_paths = _local_livery_material_paths(entry)
    material_path = material_paths[0] if material_paths else _local_livery_material_path(entry)
    if not material_path.exists():
        raise FileNotFoundError(f"Material file not found: {material_path}")
    local_material_map_sync = _ensure_livery_material_maps_in_local_skin(entry, game_root, finish=finish)
    texture_paths = _livery_material_texture_paths(entry, material_path)
    material_results: list[dict[str, Any]] = []
    changed = False
    backup = None
    for row in material_paths or [material_path]:
        material_data = row.read_bytes()
        patched = material_data
        for finish_key, param_name in LIVERY_MATERIAL_FINISH_PARAMS.items():
            patched = _set_material_float_parameters(patched, {param_name}, float(finish[finish_key]))
        slot_replacements = _livery_material_texture_slot_replacements(entry, texture_paths)
        patched = _activate_livery_material_texture_overrides(patched, slot_replacements)
        patched = _set_material_texture_slots_nested(patched, slot_replacements)
        row_changed = patched != material_data
        row_backup = None
        if row_changed:
            row_backup = _write_game_bytes(row, patched, label="skin-local material finish")
            changed = True
            if backup is None:
                backup = row_backup
        material_results.append({
            "path": str(row),
            "changed": row_changed,
            "backup": str(row_backup) if row_backup else "",
        })
    map_update = _write_livery_material_finish_maps(entry, material_path, finish, texture_paths)
    project_map_update = _write_livery_material_finish_project_pngs(data, texture_paths)
    project_path = _save_livery_project_material_finish(data, finish)
    paint_override_sync = _disable_livery_evo_paint_override_for_entry(entry, mark_sync=False)
    changed = bool(
        changed
        or previous_finish != finish
        or int(map_update.get("updated") or 0) > 0
        or int(local_material_map_sync.get("copied") or 0) > 0
        or int(local_material_map_sync.get("generated") or 0) > 0
        or paint_override_sync.get("changed") is True
    )
    entry["material_finish"] = _json_ready(finish)
    if changed:
        _mark_my_skin_game_sync_required(entry, "Material finish changed", "material_finish")
    game_update_required = changed and bool(entry.get("active"))
    skins[index] = entry
    settings_path = save_my_skins(skins)
    return {
        "changed": changed,
        "gameUpdateRequired": game_update_required,
        "backup": str(backup) if backup else "",
        "materialPath": str(material_path),
        "materialPaths": [str(row) for row in material_paths],
        "materials": material_results,
        "projectPath": str(project_path) if project_path else "",
        "localRoot": str(local_root) if local_root else "",
        "materialFinish": finish,
        "localExtSkinMaterialSync": local_ext_skin_sync,
        "gt3rsWeissachStickerSync": weissach_sticker_sync,
        "gt3rsWeissachCarbonSync": weissach_carbon_sync,
        "caymanGt4rsWeissachCarbonSync": cayman_weissach_carbon_sync,
        "paintOverrideSync": paint_override_sync,
        "materialMapLocalSync": local_material_map_sync,
        "materialMapUpdate": map_update,
        "projectMaterialMapUpdate": project_map_update,
        "gameSync": entry.get("game_sync") or {},
        "settingsPath": settings_path,
        "item": entry,
    }


def _proto_roundtrip_fields(raw: bytes) -> list[ProtoField] | None:
    try:
        fields = parse_fields(raw)
        if build_fields(fields) == raw:
            return fields
    except Exception:
        return None
    return None


def _is_evo_paint_color_ref(text: str | None) -> bool:
    return "oemmultilayercolor" in str(text or "").lower()


def _normalized_material_ref(value: str) -> str:
    return norm_slashes(str(value or "")).lower()


def _fields_target_ext_skin(fields: list[ProtoField], target_materials: set[str]) -> bool:
    for field in fields:
        if field.wire != 2 or not isinstance(field.value, (bytes, bytearray)):
            continue
        text = is_printable_utf8(bytes(field.value))
        if text is None:
            continue
        normalized = _normalized_material_ref(text)
        if normalized in target_materials:
            return True
        if text.strip().upper().replace(" ", "_") == "EXT_SKIN":
            return True
    return False


def _design_slot_targets_ext_skin(slot_fields: list[ProtoField], target_materials: set[str]) -> bool:
    slot_id = ""
    slot_material = ""
    for field in slot_fields:
        if field.wire != 2 or not isinstance(field.value, (bytes, bytearray)):
            continue
        text = is_printable_utf8(bytes(field.value))
        if text is None:
            continue
        if field.number == 1:
            slot_id = text
        elif field.number == 3:
            slot_material = text
    if _is_ext_skin_design_slot(slot_id):
        return True
    return bool(slot_material and _normalized_material_ref(slot_material) in target_materials)


def _neutral_ext_skin_design_paint_control(slot_fields: list[ProtoField]) -> bytes:
    has_colour_table = any(field.number in {4, 5} for field in slot_fields)
    selector_state = (
        bytes.fromhex("080258016001680170017801800101")
        if has_colour_table
        else b""
    )
    return build_fields([
        ProtoField(1, 2, selector_state),
        ProtoField(2, 2, b""),
        ProtoField(3, 2, b""),
    ])


def _strip_ext_skin_design_paint_controls(raw: bytes, target_materials: set[str]) -> tuple[bytes, int, int]:
    fields = _proto_roundtrip_fields(raw)
    if fields is None:
        return raw, 0, 0
    changed = False
    removed_refs = 0
    removed_controls = 0
    out: list[ProtoField] = []
    for field in fields:
        if field.number == 3 and field.wire == 2 and isinstance(field.value, (bytes, bytearray)):
            slot_fields = _proto_roundtrip_fields(bytes(field.value))
            if slot_fields is not None and _design_slot_targets_ext_skin(slot_fields, target_materials):
                slot_out: list[ProtoField] = []
                slot_changed = False
                control_seen = False
                for slot_field in slot_fields:
                    if slot_field.number == 6 and slot_field.wire == 2 and isinstance(slot_field.value, (bytes, bytearray)):
                        control_seen = True
                        old_control = bytes(slot_field.value)
                        neutral_control, removed = _strip_evo_paint_color_refs(old_control)
                        if removed and _count_evo_paint_color_refs(neutral_control) <= 0:
                            slot_out.append(slot_field)
                            continue
                        if removed:
                            removed_refs += removed
                            removed_controls += 1
                            slot_changed = True
                        slot_out.append(ProtoField(slot_field.number, slot_field.wire, neutral_control))
                        continue
                    slot_out.append(slot_field)
                if not control_seen:
                    neutral_control = _neutral_ext_skin_design_paint_control(slot_fields)
                    if _count_evo_paint_color_refs(neutral_control) > 0:
                        slot_out.append(ProtoField(6, 2, neutral_control))
                        removed_controls += 1
                        slot_changed = True
                if slot_changed:
                    out.append(ProtoField(field.number, field.wire, build_fields(slot_out)))
                    changed = True
                    continue
        out.append(field)
    return (build_fields(out), removed_refs, removed_controls) if changed else (raw, 0, 0)


def _strip_evo_paint_color_refs(raw: bytes) -> tuple[bytes, int]:
    fields = _proto_roundtrip_fields(raw)
    if fields is None:
        return raw, 0
    changed = False
    removed = 0
    out: list[ProtoField] = []
    for field in fields:
        if field.wire == 2 and isinstance(field.value, (bytes, bytearray)):
            value = bytes(field.value)
            text = is_printable_utf8(value)
            if _is_evo_paint_color_ref(text):
                changed = True
                removed += 1
                continue
            nested, nested_removed = _strip_evo_paint_color_refs(value)
            if nested_removed:
                out.append(ProtoField(field.number, field.wire, nested))
                changed = True
                removed += nested_removed
                continue
        out.append(field)
    return (build_fields(out), removed) if changed else (raw, 0)


def _count_evo_paint_color_refs(raw: bytes) -> int:
    fields = _proto_roundtrip_fields(raw)
    if fields is None:
        return 0
    total = 0
    for field in fields:
        if field.wire == 2 and isinstance(field.value, (bytes, bytearray)):
            value = bytes(field.value)
            text = is_printable_utf8(value)
            if _is_evo_paint_color_ref(text):
                total += 1
            else:
                total += _count_evo_paint_color_refs(value)
    return total


def _strip_evo_paint_overrides_for_ext_skin(raw: bytes, target_materials: set[str]) -> tuple[bytes, int]:
    fields = _proto_roundtrip_fields(raw)
    if fields is None:
        return raw, 0
    if _fields_target_ext_skin(fields, target_materials):
        return _strip_evo_paint_color_refs(raw)
    changed = False
    removed = 0
    out: list[ProtoField] = []
    for field in fields:
        if field.wire == 2 and isinstance(field.value, (bytes, bytearray)):
            nested, nested_removed = _strip_evo_paint_overrides_for_ext_skin(bytes(field.value), target_materials)
            if nested_removed:
                out.append(ProtoField(field.number, field.wire, nested))
                changed = True
                removed += nested_removed
                continue
        out.append(field)
    return (build_fields(out), removed) if changed else (raw, 0)


def _skin_file_paths_for_kinds(entry: dict[str, Any], kinds: set[str]) -> list[tuple[str, Path]]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    paths: list[tuple[str, Path]] = []
    seen: set[str] = set()
    for kind, rel_value in _file_rel_values(files, kinds):
        rel_norm = normalize_skin_asset_rel_path(str(rel_value or ""))
        if not rel_norm or rel_norm.lower() in seen:
            continue
        seen.add(rel_norm.lower())
        path = _local_skin_asset_path(entry, rel_norm)
        if path and path.exists() and path.is_file():
            paths.append((kind, path))
    return paths


def _write_local_skin_bytes_with_backup(path: Path, data: bytes) -> Path | None:
    backup = None
    if path.exists() and path.is_file() and not is_app_managed_storage_path(path):
        backup_dir = path.parent / "_acevo_backups"
        backup_dir.mkdir(parents=True, exist_ok=True)
        stamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
        backup = backup_dir / f"{path.name}.{stamp}.bak"
        shutil.copy2(path, backup)
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_bytes(data)
    return backup


def _disable_livery_evo_paint_override_for_entry(entry: dict[str, Any], *, mark_sync: bool = True) -> dict[str, Any]:
    target_materials = _entry_ext_skin_material_refs(entry)
    if not target_materials:
        files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
        material_rel = normalize_skin_asset_rel_path(str(files.get("material") or ""))
        if material_rel:
            target_materials = {_normalized_material_ref(material_rel)}
    changed_files: list[dict[str, Any]] = []
    inspected_files: list[dict[str, Any]] = []
    total_removed = 0
    total_controls_removed = 0
    for kind, path in _skin_file_paths_for_kinds(entry, {"design", "visual_preset", "carfinalstate", "carfinalstates"}):
        old_data = path.read_bytes()
        working = old_data
        design_removed = 0
        controls_removed = 0
        if kind == "design":
            working, design_removed, controls_removed = _strip_ext_skin_design_paint_controls(working, target_materials)
            new_data, removed = working, 0
        else:
            new_data, removed = _strip_evo_paint_overrides_for_ext_skin(working, target_materials)
        removed += design_removed
        inspected_files.append({
            "kind": kind,
            "path": str(path),
            "removed": removed,
            "removedControls": controls_removed,
        })
        if new_data == old_data:
            continue
        backup = _write_local_skin_bytes_with_backup(path, new_data)
        changed_files.append({
            "kind": kind,
            "path": str(path),
            "backup": str(backup) if backup else "",
            "removed": removed,
            "removedControls": controls_removed,
        })
        total_removed += removed
        total_controls_removed += controls_removed
    if changed_files:
        entry["evo_paint_override"] = {
            "disabled": True,
            "updated_at": datetime.now().isoformat(timespec="seconds"),
            "removed_refs": total_removed,
            "removed_controls": total_controls_removed,
        }
        if mark_sync:
            _mark_my_skin_game_sync_required(entry, "EVO paint override disabled", "disable_evo_paint_override")
    return {
        "changed": bool(changed_files),
        "removedRefs": total_removed,
        "removedControls": total_controls_removed,
        "changedFiles": changed_files,
        "inspectedFiles": inspected_files,
        "materialRefs": sorted(target_materials),
        "gameSync": entry.get("game_sync") or {},
    }


def disable_livery_evo_paint_override(payload: dict[str, Any]) -> dict[str, Any]:
    data = payload if isinstance(payload, dict) else {}
    skin_key = str(data.get("skinKey") or "").strip()
    entry, skins, index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canUseEditablePreviewTools")
    target_materials = _entry_ext_skin_material_refs(entry)
    if not target_materials:
        raise ValueError("Skin has no EXT_SKIN material reference")
    result = _disable_livery_evo_paint_override_for_entry(entry)
    if result.get("changed"):
        skins[index] = entry
        save_my_skins(skins)
    return result


def _preview3d_default_scene_settings_path() -> Path | None:
    root = get_program_root()
    candidates = [
        root / "web" / "assets" / "preview3d_scenes.json",
        root / "_internal" / "web" / "assets" / "preview3d_scenes.json",
        Path(__file__).resolve().parent / "web" / "assets" / "preview3d_scenes.json",
    ]
    for candidate in candidates:
        if candidate.exists():
            return candidate
    return None


def get_preview3d_scene_settings_path() -> Path:
    return get_livery_library_root() / "Settings" / "preview3d_scenes.json"


def _read_preview3d_default_scene_settings() -> dict[str, Any]:
    path = _preview3d_default_scene_settings_path()
    if path and path.exists():
        try:
            data = json.loads(path.read_text(encoding="utf-8"))
            if isinstance(data, dict):
                return data
        except Exception:
            pass
    return {"default": "light", "scenes": {"light": {}, "dark": {}, "studio": {}}}


def _merge_dicts(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
    result = dict(base)
    for key, value in override.items():
        if isinstance(value, dict) and isinstance(result.get(key), dict):
            result[key] = _merge_dicts(result[key], value)
        else:
            result[key] = value
    return result


def _clamped_float(value: Any, fallback: float, lo: float, hi: float) -> float:
    try:
        numeric = float(value)
    except Exception:
        numeric = fallback
    return max(lo, min(hi, numeric))


def _hex_color(value: Any, fallback: str) -> str:
    text = str(value or "").strip()
    if re.fullmatch(r"#[0-9a-fA-F]{6}", text):
        return text.lower()
    return fallback


def _vector3_list(value: Any, fallback: Any = None, lo: float = -50.0, hi: float = 50.0) -> list[float]:
    source = value if isinstance(value, list) else []
    base = fallback if isinstance(fallback, list) else [0.0, 0.0, 0.0]
    return [
        _clamped_float(source[index] if index < len(source) else None, _clamped_float(base[index] if index < len(base) else 0.0, 0.0, lo, hi), lo, hi)
        for index in range(3)
    ]


def _sanitize_preview3d_scene(scene: dict[str, Any], fallback: dict[str, Any]) -> dict[str, Any]:
    data = scene if isinstance(scene, dict) else {}
    base = fallback if isinstance(fallback, dict) else {}
    floor = data.get("floor") if isinstance(data.get("floor"), dict) else {}
    base_floor = base.get("floor") if isinstance(base.get("floor"), dict) else {}
    renderer = data.get("renderer") if isinstance(data.get("renderer"), dict) else {}
    base_renderer = base.get("renderer") if isinstance(base.get("renderer"), dict) else {}
    contact_shadow = data.get("contactShadow") if isinstance(data.get("contactShadow"), dict) else {}
    base_contact_shadow = base.get("contactShadow") if isinstance(base.get("contactShadow"), dict) else {}
    real_shadow = data.get("realShadow") if isinstance(data.get("realShadow"), dict) else {}
    base_real_shadow = base.get("realShadow") if isinstance(base.get("realShadow"), dict) else {}
    environment = data.get("environment") if isinstance(data.get("environment"), dict) else {}
    base_environment = base.get("environment") if isinstance(base.get("environment"), dict) else {}
    material_look = data.get("materialLook") if isinstance(data.get("materialLook"), dict) else {}
    base_material_look = base.get("materialLook") if isinstance(base.get("materialLook"), dict) else {}

    def light(key: str, lo: float, hi: float) -> dict[str, Any]:
        source = data.get(key) if isinstance(data.get(key), dict) else {}
        base_light = base.get(key) if isinstance(base.get(key), dict) else {}
        return _merge_dicts(
            base_light,
            {
                "intensity": _clamped_float(
                    source.get("intensity"),
                    _clamped_float(base_light.get("intensity"), 1.0, lo, hi),
                    lo,
                    hi,
                ),
                "position": _vector3_list(source.get("position"), base_light.get("position"), -20.0, 20.0),
            },
        )

    return _merge_dicts(
        base,
        {
            "renderer": _merge_dicts(
                base_renderer,
                {"exposure": _clamped_float(renderer.get("exposure"), _clamped_float(base_renderer.get("exposure"), 1.0, 0.1, 4.0), 0.1, 4.0)},
            ),
            "background": _hex_color(data.get("background"), _hex_color(base.get("background"), "#101113")),
            "floor": _merge_dicts(
                base_floor,
                {
                    "color": _hex_color(floor.get("color"), _hex_color(base_floor.get("color"), "#101113")),
                    "visible": floor.get("visible") is not False,
                },
            ),
            "contactShadow": _merge_dicts(
                base_contact_shadow,
                {
                    "visible": contact_shadow.get("visible", base_contact_shadow.get("visible", True)) is not False,
                    "opacity": _clamped_float(contact_shadow.get("opacity"), _clamped_float(base_contact_shadow.get("opacity"), 0.4, 0.0, 1.0), 0.0, 1.0),
                    "scaleX": _clamped_float(contact_shadow.get("scaleX"), _clamped_float(base_contact_shadow.get("scaleX"), 1.0, 0.1, 8.0), 0.1, 8.0),
                    "scaleZ": _clamped_float(contact_shadow.get("scaleZ"), _clamped_float(base_contact_shadow.get("scaleZ"), 1.0, 0.1, 8.0), 0.1, 8.0),
                },
            ),
            "realShadow": _merge_dicts(
                base_real_shadow,
                {
                    "enabled": bool(real_shadow.get("enabled", base_real_shadow.get("enabled", False))),
                    "opacity": _clamped_float(real_shadow.get("opacity"), _clamped_float(base_real_shadow.get("opacity"), 0.34, 0.0, 1.0), 0.0, 1.0),
                    "radius": _clamped_float(real_shadow.get("radius"), _clamped_float(base_real_shadow.get("radius"), 5.0, 0.0, 20.0), 0.0, 20.0),
                    "bias": _clamped_float(real_shadow.get("bias"), _clamped_float(base_real_shadow.get("bias"), -0.0002, -0.01, 0.01), -0.01, 0.01),
                    "normalBias": _clamped_float(real_shadow.get("normalBias"), _clamped_float(base_real_shadow.get("normalBias"), 0.02, 0.0, 0.2), 0.0, 0.2),
                    "mapSize": int(_clamped_float(real_shadow.get("mapSize"), _clamped_float(base_real_shadow.get("mapSize"), 2048, 256, 4096), 256, 4096)),
                    "cameraSize": _clamped_float(real_shadow.get("cameraSize"), _clamped_float(base_real_shadow.get("cameraSize"), 8.0, 1.0, 40.0), 1.0, 40.0),
                },
            ),
            "environment": _merge_dicts(
                base_environment,
                {
                    "enabled": bool(environment.get("enabled", base_environment.get("enabled", False))),
                    "intensity": _clamped_float(environment.get("intensity"), _clamped_float(base_environment.get("intensity"), 0.0, 0.0, 4.0), 0.0, 4.0),
                },
            ),
            "hemi": light("hemi", 0.0, 4.0),
            "key": light("key", 0.0, 6.0),
            "fill": light("fill", 0.0, 6.0),
            "rim": light("rim", 0.0, 6.0),
            "materialLook": _merge_dicts(
                base_material_look,
                {
                    "evoApproxEnabled": bool(material_look.get("evoApproxEnabled", base_material_look.get("evoApproxEnabled", False))),
                    "maskThreshold": _clamped_float(material_look.get("maskThreshold"), _clamped_float(base_material_look.get("maskThreshold"), 0.08, 0.0, 0.5), 0.0, 0.5),
                    "paintBrightness": _clamped_float(material_look.get("paintBrightness"), _clamped_float(base_material_look.get("paintBrightness"), 1.0, 0.2, 2.5), 0.2, 2.5),
                    "paintSpecular": _clamped_float(material_look.get("paintSpecular"), _clamped_float(base_material_look.get("paintSpecular"), 0.35, 0.0, 3.0), 0.0, 3.0),
                    "paintLightInfluence": _clamped_float(material_look.get("paintLightInfluence"), _clamped_float(base_material_look.get("paintLightInfluence"), 0.0, 0.0, 1.0), 0.0, 1.0),
                    "paintRimStrength": _clamped_float(material_look.get("paintRimStrength"), _clamped_float(base_material_look.get("paintRimStrength"), 0.012, 0.0, 0.25), 0.0, 0.25),
                    "paintHighlightStrength": _clamped_float(material_look.get("paintHighlightStrength"), _clamped_float(base_material_look.get("paintHighlightStrength"), 0.025, 0.0, 0.5), 0.0, 0.5),
                    "paintToneMapped": bool(material_look.get("paintToneMapped", base_material_look.get("paintToneMapped", False))),
                    "carbonScale": _clamped_float(material_look.get("carbonScale"), _clamped_float(base_material_look.get("carbonScale"), 18.0, 1.0, 80.0), 1.0, 80.0),
                    "carbonBrightness": _clamped_float(material_look.get("carbonBrightness"), _clamped_float(base_material_look.get("carbonBrightness"), 1.0, 0.2, 2.5), 0.2, 2.5),
                    "matteColor": _hex_color(material_look.get("matteColor"), _hex_color(base_material_look.get("matteColor"), "#15181b")),
                    "glassOpacity": _clamped_float(material_look.get("glassOpacity"), _clamped_float(base_material_look.get("glassOpacity"), 0.82, 0.05, 1.0), 0.05, 1.0),
                    "lightEmissive": _clamped_float(material_look.get("lightEmissive"), _clamped_float(base_material_look.get("lightEmissive"), 1.0, 0.0, 4.0), 0.0, 4.0),
                    "rimMetalness": _clamped_float(material_look.get("rimMetalness"), _clamped_float(base_material_look.get("rimMetalness"), 0.58, 0.0, 1.0), 0.0, 1.0),
                    "tyreRoughness": _clamped_float(material_look.get("tyreRoughness"), _clamped_float(base_material_look.get("tyreRoughness"), 0.7, 0.05, 1.0), 0.05, 1.0),
                    "tyreEnvMapIntensity": _clamped_float(material_look.get("tyreEnvMapIntensity"), _clamped_float(base_material_look.get("tyreEnvMapIntensity"), 0.0, 0.0, 1.0), 0.0, 1.0),
                    "environmentIntensity": _clamped_float(material_look.get("environmentIntensity"), _clamped_float(base_material_look.get("environmentIntensity"), 0.0, 0.0, 4.0), 0.0, 4.0),
                    "paintReflectionStrength": _clamped_float(material_look.get("paintReflectionStrength"), _clamped_float(base_material_look.get("paintReflectionStrength"), 0.32, 0.0, 2.0), 0.0, 2.0),
                    "paintStripStrength": _clamped_float(material_look.get("paintStripStrength"), _clamped_float(base_material_look.get("paintStripStrength"), 0.72, 0.0, 3.0), 0.0, 3.0),
                    "paintStripSharpness": _clamped_float(material_look.get("paintStripSharpness"), _clamped_float(base_material_look.get("paintStripSharpness"), 24.0, 2.0, 96.0), 2.0, 96.0),
                },
            ),
        },
    )


def _normalize_preview3d_scene_settings(settings: dict[str, Any] | None, defaults: dict[str, Any] | None = None) -> dict[str, Any]:
    default_data = defaults if isinstance(defaults, dict) else _read_preview3d_default_scene_settings()
    default_scenes = default_data.get("scenes") if isinstance(default_data.get("scenes"), dict) else {}
    data = settings if isinstance(settings, dict) else {}
    scenes = data.get("scenes") if isinstance(data.get("scenes"), dict) else {}
    default_scene_name = str(data.get("default") or default_data.get("default") or "light").strip().lower()
    if default_scene_name not in PREVIEW3D_SCENE_NAMES:
        default_scene_name = "light"
    return {
        "default": default_scene_name,
        "scenes": {
            scene_name: _sanitize_preview3d_scene(
                scenes.get(scene_name) if isinstance(scenes.get(scene_name), dict) else {},
                default_scenes.get(scene_name) if isinstance(default_scenes.get(scene_name), dict) else {},
            )
            for scene_name in PREVIEW3D_SCENE_NAMES
        },
    }


def load_preview3d_scene_settings() -> dict[str, Any]:
    defaults = _normalize_preview3d_scene_settings(_read_preview3d_default_scene_settings())
    path = get_preview3d_scene_settings_path()
    stored: dict[str, Any] = {}
    exists = path.exists()
    if exists:
        data = json.loads(path.read_text(encoding="utf-8"))
        stored = data if isinstance(data, dict) else {}
    settings = _normalize_preview3d_scene_settings(stored, defaults)
    return {
        "settings": _json_ready(settings),
        "defaults": _json_ready(defaults),
        "path": str(path),
        "exists": exists,
    }


def save_preview3d_scene_settings(settings: dict[str, Any]) -> dict[str, Any]:
    defaults = _normalize_preview3d_scene_settings(_read_preview3d_default_scene_settings())
    normalized = _normalize_preview3d_scene_settings(settings if isinstance(settings, dict) else {}, defaults)
    path = get_preview3d_scene_settings_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    backup = safe_write_text(path, json.dumps(_json_ready(normalized), indent=2, ensure_ascii=False))
    return {
        "settings": _json_ready(normalized),
        "defaults": _json_ready(defaults),
        "path": str(path),
        "backup": str(backup) if backup else "",
    }


def generate_preview3d_car_ao_texture(root: str | Path | None = None, car_model: str | None = None, overwrite: bool = False) -> dict[str, Any]:
    car_id = preview3d.safe_segment(car_model, "car")
    if not car_id or car_id == "car":
        raise ValueError("Car model is required")
    game_root = normalize_game_root(root or get_default_root_folder())
    source = preview3d_textures.car_ao_game_texture_path(game_root, car_id)
    target = preview3d_textures.car_ao_library_path(get_converted_textures_root(), car_id)
    status_before = preview3d_textures.car_ao_texture_status(get_converted_textures_root(), game_root, car_id)
    if not source.exists():
        raise FileNotFoundError(f"Game AO texture not found: {source}")
    if not source.with_suffix(".texturemips").exists():
        raise FileNotFoundError(f"Game AO texturemips not found: {source.with_suffix('.texturemips')}")
    converted = convert_texture_to_png(source, target, overwrite=overwrite, create_dds=False)
    status_after = preview3d_textures.car_ao_texture_status(get_converted_textures_root(), game_root, car_id)
    return {
        **status_after,
        "sourceTexturePath": str(source),
        "targetPngPath": str(target),
        "converted": converted,
        "status": "skipped" if converted.get("skipped") else "generated",
        "reason": converted.get("reason") or "",
        "hadExistingPng": bool(status_before.get("targetPngExists")),
    }


def _update_livery_preview_project_variant_metadata(
    project_path: str | Path,
    variant_parts: dict[str, Any],
    variant_analysis: dict[str, Any] | None = None,
) -> None:
    path = Path(str(project_path or ""))
    if not path.exists():
        return
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return
    if not isinstance(data, dict):
        return
    if variant_parts.get("meshRefs"):
        data["hideExteriorModel"] = bool(variant_parts.get("hideExteriorModel"))
        data["variantPartPreview"] = _json_ready(variant_parts)
    else:
        data.pop("hideExteriorModel", None)
        data.pop("variantPartPreview", None)
        data.pop("hiddenExteriorMaterialNames", None)
        data.pop("hiddenExteriorComponentMaterials", None)
    analysis = variant_analysis if isinstance(variant_analysis, dict) else {}
    if analysis:
        data["variantPolicy"] = _json_ready(analysis.get("variantPolicy") or {})
        data["variantPolicyVersion"] = analysis.get("variantPolicyVersion")
        data["surfacePolicies"] = _json_ready(analysis.get("surfacePolicies") or [])
    data["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    path.write_text(json.dumps(_json_ready(data), indent=2, ensure_ascii=False), encoding="utf-8")


def _clear_livery_preview_project_variant_uv(project_path: str | Path, project: dict[str, Any]) -> None:
    for key in ("variantUvTemplateName", "variantUvTemplatePath", "variantUvTemplate"):
        project.pop(key, None)
    path = Path(str(project_path or ""))
    if not path.exists():
        return
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return
    if not isinstance(data, dict):
        return
    for key in ("variantUvTemplateName", "variantUvTemplatePath", "variantUvTemplate"):
        data.pop(key, None)
    data["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    path.write_text(json.dumps(_json_ready(data), indent=2, ensure_ascii=False), encoding="utf-8")


def _clear_livery_preview_project_parts_uv(project_path: str | Path, project: dict[str, Any]) -> None:
    for key in ("partsUvTemplateName", "partsUvTemplatePath", "partsUvTemplate"):
        project.pop(key, None)
    uv_templates.clear_preview_project_parts_uv_template(project_path)


def _sync_livery_preview_hidden_materials(
    project_path: str | Path,
    hidden_materials: list[str],
    hidden_exterior_materials: list[str] | None = None,
) -> None:
    path = Path(str(project_path or ""))
    if not path.exists():
        return
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return
    if not isinstance(data, dict):
        return
    clean = [str(row or "").strip() for row in hidden_materials if str(row or "").strip()]
    if clean:
        data["hiddenMaterialNames"] = clean
    else:
        data.pop("hiddenMaterialNames", None)
    exterior_clean = [
        str(row or "").strip()
        for row in (hidden_exterior_materials or [])
        if str(row or "").strip()
    ]
    if exterior_clean:
        data["hiddenExteriorMaterialNames"] = exterior_clean
    else:
        data.pop("hiddenExteriorMaterialNames", None)
    data["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    path.write_text(json.dumps(_json_ready(data), indent=2, ensure_ascii=False), encoding="utf-8")


def _set_variant_analysis_uv_policy(variant_analysis: dict[str, Any], uv_policy: str) -> None:
    policy_text = str(uv_policy or "").strip()
    if not policy_text or not isinstance(variant_analysis, dict):
        return
    for surface in variant_analysis.get("surfacePolicies") or []:
        if isinstance(surface, dict) and str(surface.get("key") or "").strip().lower() == "ext_skin_c":
            surface["uvPolicy"] = policy_text
    policy = variant_analysis.get("variantPolicy") if isinstance(variant_analysis.get("variantPolicy"), dict) else {}
    for surface in policy.get("surfaces") or []:
        if isinstance(surface, dict) and str(surface.get("key") or "").strip().lower() == "ext_skin_c":
            surface["uvPolicy"] = policy_text


def _sync_livery_preview_active_ao(
    entry: dict[str, Any],
    game_root: Path,
    skin_folder: Path | None,
    project_folder: Path,
    project_path: str | Path,
    project: dict[str, Any],
    *,
    local_only: bool,
    overwrite: bool = False,
) -> dict[str, Any]:
    source = _find_ao_texture_for_skin(game_root, entry, skin_folder, local_only=local_only)
    target = project_folder / preview3d.LIVE_AO_TEXTURE_NAME
    result: dict[str, Any] = {
        "label": "ext_skin_ao",
        "source": str(source) if source else "",
        "target": str(target),
        "status": "skipped",
        "reason": "",
    }
    if source is None:
        result["reason"] = "AO source missing"
        return result
    if not source.with_suffix(".texturemips").exists():
        result["reason"] = "AO texturemips missing"
        return result
    try:
        should_convert = overwrite or not target.exists() or source.stat().st_mtime > target.stat().st_mtime
    except Exception:
        should_convert = overwrite or not target.exists()
    if should_convert:
        converted = convert_texture_to_png(source, target, overwrite=True, create_dds=False)
        result["status"] = "imported" if not converted.get("skipped") else "skipped"
        result["reason"] = converted.get("reason") or ""
    else:
        result["status"] = "skipped"
        result["reason"] = "target current"
    if target.exists():
        project["liveAoTexturePath"] = str(target)
        project["aoTexturePath"] = str(target)
        path = Path(str(project_path or ""))
        if path.exists():
            try:
                data = json.loads(path.read_text(encoding="utf-8"))
                if isinstance(data, dict):
                    data["liveAoTexturePath"] = _livery_project_rel(project_folder, target)
                    data["aoTexturePath"] = _livery_project_rel(project_folder, target)
                    data["updatedAt"] = datetime.now().isoformat(timespec="seconds")
                    path.write_text(json.dumps(_json_ready(data), indent=2, ensure_ascii=False), encoding="utf-8")
            except Exception:
                pass
    return result


def prepare_livery_preview_project(skin_key: str, root: str | Path | None = None) -> dict[str, Any]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    game_root_text = str(root or entry.get("game_root") or get_default_root_folder()).strip()
    if not game_root_text:
        raise ValueError("Game root is required")
    game_root = normalize_game_root(game_root_text)
    if not game_root.exists():
        raise FileNotFoundError(f"Game root not found: {game_root}")
    entry_project_type = project_types.project_type_for_entry(entry)
    entry_capabilities = project_capabilities.capabilities_for_entry(
        entry,
        game_root,
        livery_library_root=get_livery_library_root(),
        official_livery_library_root=get_official_livery_library_root(create=False),
    )
    entry_capability_details = project_capabilities.capability_details_for_entry(
        entry,
        game_root,
        livery_library_root=get_livery_library_root(),
        official_livery_library_root=get_official_livery_library_root(create=False),
    )
    entry["capabilities"] = _json_ready(entry_capabilities)
    entry["capabilityDetails"] = _json_ready(entry_capability_details)
    policy = skin_policy.policy_for_skin(entry)
    editable_preview = bool(policy.get("canUseEditablePreviewTools"))
    locked_project_workspace = official_to_advanced_import.uses_locked_project_workspace(entry)
    official_role = official_external_workflow.role_for_entry(entry)
    if (
        entry_project_type == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY
        and official_role in {official_external_workflow.ROLE_PROJECT, official_external_workflow.ROLE_LIBRARY}
    ):
        read_only_official = official_role == official_external_workflow.ROLE_LIBRARY
        material_source_folder: Path
        if read_only_official:
            material_source_folder = _entry_local_root(entry)
            if material_source_folder is None:
                raise FileNotFoundError("Read-only Official livery folder not found")
            project_folder = (
                get_read_only_livery_preview_root()
                / "Official"
                / preview3d.safe_segment(entry.get("car_model"), "car")
                / preview3d.safe_segment(entry.get("skin_id") or entry.get("display_name"), "skin")
            )
            preview_assets = official_external_project_assets.prepare_read_only_preview_cache(
                material_source_folder,
                project_folder,
                entry,
                preview_assets_root=get_official_preview_assets_root(create=True),
            )
        else:
            project_folder = _livery_project_folder_for_entry(entry)
            material_source_folder = project_folder
            preview_assets = official_external_project_assets.prepare_project_preview_assets(
                project_folder,
                entry,
                preview_assets_root=get_official_preview_assets_root(create=True),
                overwrite_uv=False,
            )
        project_path = project_folder / official_external_project_assets.PREVIEW_PROJECT
        project = {}
        if project_path.exists() and project_path.is_file():
            try:
                loaded_project = json.loads(project_path.read_text(encoding="utf-8"))
                project = loaded_project if isinstance(loaded_project, dict) else {}
            except Exception:
                project = {}
        if not project:
            project = {
                "version": 1,
                "skinKey": entry.get("key") or skin_key,
                "displayName": entry.get("display_name") or entry.get("skin_id") or "Official Livery",
                "skinId": entry.get("skin_id") or "",
                "projectType": entry_project_type,
                "carModel": entry.get("car_model") or "",
                "carContentPath": entry.get("car_content_path") or "",
                "gameRoot": str(game_root),
                "projectFolder": str(project_folder),
                "projectPath": str(project_path),
                "materialName": "EXT_SKIN",
                "bannerMaterialName": "EXT_BANNER",
            }
        inspected = preview3d.inspect_project(project)
        normalized_project = inspected.get("project") if isinstance(inspected.get("project"), dict) else {}
        project = {**project, **normalized_project}
        project["policy"] = _json_ready(policy)
        project["projectType"] = entry_project_type
        project["capabilities"] = _json_ready(entry_capabilities)
        project["capabilityDetails"] = _json_ready(entry_capability_details)
        project["readOnly"] = not editable_preview
        project["projectState"] = policy.get("projectState")
        project["projectFolder"] = str(project_folder)
        project["projectPath"] = str(project_path)
        project["materialName"] = project.get("materialName") or "EXT_SKIN"
        project["bannerMaterialName"] = project.get("bannerMaterialName") or "EXT_BANNER"
        official_material_channels = official_external_project_assets.official_material_preview_config(
            material_source_folder,
            material_batch=_official_external_entry_material_batch(entry),
        )
        project["officialMaterialChannels"] = _json_ready(official_material_channels)
        if official_material_channels.get("available"):
            red_channel = official_material_channels.get("red") if isinstance(official_material_channels.get("red"), dict) else {}
            project["bodyBaseColor"] = preview3d.clean_hex_color(red_channel.get("baseColor"))
        saved_car_color = _official_external_entry_saved_car_body_color(entry, game_root)
        if saved_car_color.get("supported") and saved_car_color.get("color"):
            project["bodyBaseColor"] = preview3d.clean_hex_color(saved_car_color.get("color"))
        material_surfaces = _official_external_entry_material_surfaces(entry)
        preview_material_surfaces: list[dict[str, Any]] = []
        preview_surface_root = project_folder / "preview-material-surfaces"
        for surface in material_surfaces:
            if not isinstance(surface, dict) or surface.get("bodyColorControlled") is not True:
                continue
            batch_name = str(surface.get("batchName") or "").strip()
            source_text = str(surface.get("splatTextureSource") or "").strip()
            detail_path = project_folder / "EXT_Skin_D.png" if batch_name.upper() == "EXT_SKIN" else None
            if detail_path is None or not detail_path.is_file():
                source = Path(source_text) if source_text else None
                filename = str(surface.get("splatTexture") or "").strip()
                if source and source.is_file() and filename:
                    target = preview_surface_root / filename
                    if not target.is_file() or target.stat().st_mtime < source.stat().st_mtime:
                        preview_surface_root.mkdir(parents=True, exist_ok=True)
                        convert_texture_to_png(source, target, overwrite=True, create_dds=False)
                    detail_path = target if target.is_file() else None
            preview_material_surfaces.append({
                **_json_ready(surface),
                "detailTexturePath": str(detail_path) if detail_path and detail_path.is_file() else "",
            })
        project["officialPreviewMaterialSurfaces"] = preview_material_surfaces
        body_color_mode = _body_base_color_mode_for_livery_preview(
            entry,
            game_root,
            project,
            entry_project_type,
            editable_preview=editable_preview,
        )
        if saved_car_color.get("supported"):
            body_color_mode = {
                "mode": "saved_car_synced",
                "enabled": editable_preview,
                "source": saved_car_color.get("source") or "saved_car",
                "reason": "Body Color is synchronized with the active AC EVO SavedCar",
                "savedCar": _json_ready(saved_car_color),
            }
        elif editable_preview:
            body_color_mode = {
                "mode": "official_external_red_channel",
                "enabled": True,
                "source": "external_livery_json",
                "reason": "This car uses a fixed Official livery color",
                "savedCar": _json_ready(saved_car_color),
            }
        project["bodyBaseColorMode"] = body_color_mode.get("mode") or BODY_BASE_COLOR_MODE_UNKNOWN
        project["bodyBaseColorEnabled"] = bool(body_color_mode.get("enabled"))
        project["bodyBaseColorCapability"] = _json_ready(body_color_mode)
        _write_livery_preview_project_fields(project_path, {
            "bodyBaseColorMode": project["bodyBaseColorMode"],
            "bodyBaseColorEnabled": project["bodyBaseColorEnabled"],
            "bodyBaseColorCapability": project["bodyBaseColorCapability"],
            "officialPreviewMaterialSurfaces": preview_material_surfaces,
        })
        if (project_folder / uv_templates.UV_TEMPLATE_NAME).is_file():
            uv_path = project_folder / uv_templates.UV_TEMPLATE_NAME
            project["uvTemplateName"] = uv_templates.UV_TEMPLATE_NAME
            project["uvTemplatePath"] = str(uv_path)
            project["uvTemplate"] = {
                "path": str(uv_path),
                "name": uv_path.name,
                "exists": True,
                "source": "official_preview_assets",
            }
        warnings = list(inspected.get("warnings") or [])
        warnings.extend(str(row) for row in preview_assets.get("warnings", []) or [])
        result = {
            **inspected,
            "project": project,
            "projectPath": str(project_path),
            "projectFolder": str(project_folder),
            "projectType": entry_project_type,
            "capabilities": _json_ready(entry_capabilities),
            "capabilityDetails": _json_ready(entry_capability_details),
            "policy": _json_ready(policy),
            "previewAssets": preview_assets,
            "readOnly": not editable_preview,
            "warnings": list(dict.fromkeys(warnings)),
        }
        if not read_only_official:
            _update_official_external_project_manifest(
                project_folder,
                official_external_project_assets.inspect_project(project_folder),
                preview_assets=preview_assets,
            )
        return result
    preview_root = get_livery_projects_root() if editable_preview or locked_project_workspace else get_read_only_livery_preview_root()
    result = preview3d.prepare_livery_project(preview_root, game_root, entry)
    warnings = result.setdefault("warnings", [])
    project = result.get("project") if isinstance(result.get("project"), dict) else {}
    project["policy"] = _json_ready(policy)
    project["projectType"] = entry_project_type
    project["capabilities"] = _json_ready(entry_capabilities)
    project["capabilityDetails"] = _json_ready(entry_capability_details)
    project["readOnly"] = not editable_preview
    project["projectState"] = policy.get("projectState")
    result["projectType"] = entry_project_type
    result["capabilities"] = _json_ready(entry_capabilities)
    result["capabilityDetails"] = _json_ready(entry_capability_details)
    body_color_mode = _body_base_color_mode_for_livery_preview(
        entry,
        game_root,
        project,
        entry_project_type,
        editable_preview=editable_preview,
    )
    project["bodyBaseColorMode"] = body_color_mode.get("mode") or BODY_BASE_COLOR_MODE_UNKNOWN
    project["bodyBaseColorEnabled"] = bool(body_color_mode.get("enabled"))
    project["bodyBaseColorCapability"] = _json_ready(body_color_mode)
    _write_livery_preview_project_fields(result.get("projectPath") or project.get("projectPath"), {
        "bodyBaseColorMode": project["bodyBaseColorMode"],
        "bodyBaseColorEnabled": project["bodyBaseColorEnabled"],
        "bodyBaseColorCapability": project["bodyBaseColorCapability"],
    })
    hidden_preview_materials = list(dict.fromkeys([
        *_gt3rs_standard_preview_hidden_materials(entry),
        *_gt3rs_weissach_preview_hidden_materials(entry),
        *_sf25_preview_hidden_materials(entry),
        *_f2004_preview_hidden_materials(entry),
    ]))
    hidden_exterior_materials: list[str] = []
    if hidden_preview_materials:
        project["hiddenMaterialNames"] = hidden_preview_materials
    else:
        project.pop("hiddenMaterialNames", None)
    if not editable_preview and not locked_project_workspace:
        warnings.append("Read-only installed livery: edit tools and LiveryProjects workspace are disabled.")
    elif locked_project_workspace:
        warnings.append("Baked Official artwork is locked; Advanced tyre, driver and numberplate add-ons remain editable.")
    numberplate_support = _my_skin_numberplate_support(entry, game_root)
    project["numberplateSupport"] = _json_ready(numberplate_support)
    project["numberplateVariant"] = _json_ready(entry.get("numberplate_variant") or {})
    if not isinstance(project.get("materialFinish"), dict):
        try:
            material_path = _local_livery_material_path(entry)
            project["materialFinish"] = _livery_material_finish_from_material(material_path, _entry_local_root(entry) or game_root)
        except Exception:
            project["materialFinish"] = _normalize_livery_material_finish(entry.get("material_finish") if isinstance(entry.get("material_finish"), dict) else {})
    result["numberplateSupport"] = _json_ready(numberplate_support)
    project_folder = Path(str(result.get("projectFolder") or project.get("projectFolder") or ""))
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    skin_folder = _local_skin_asset_path(entry, str(files.get("skin_folder") or ""))
    local_root = _entry_local_root(entry)
    if skin_folder is None or not skin_folder.exists():
        skin_folder = _skin_file_disk_path(game_root, entry, str(files.get("skin_folder") or ""), prefer_local=False)
    local_only = bool(skin_folder and local_root and _is_path_within(skin_folder, local_root))
    if editable_preview and local_only:
        _ensure_livery_material_maps_in_local_skin(entry, game_root)
    texture_specs = _livery_project_texture_specs(game_root, entry, project, skin_folder, local_only=local_only, include_banner=not editable_preview)
    if not editable_preview and not locked_project_workspace:
        texture_specs = [
            spec
            for spec in texture_specs
            if str(spec.get("stem") or "").strip().lower() in {"ext_skin_c", "ext_banner_c"}
        ]
    _update_livery_project_material_texture_metadata(result, texture_specs)
    if not editable_preview and not locked_project_workspace:
        result["readOnlyTextureSync"] = _sync_read_only_livery_preview_textures(game_root, entry, project, skin_folder, local_only=local_only)
    exterior = result.get("exteriorMesh") if isinstance(result.get("exteriorMesh"), dict) else {}
    exterior_mesh = Path(str(exterior.get("path") or ""))
    variant_parts: dict[str, Any] = {"compatibleParts": [], "meshRefs": [], "skinMeshRefs": [], "warnings": []}
    variant_analysis: dict[str, Any] = {}
    shared_additive_variant = False
    variant_ao_additive = False
    full_variant_model = False
    variant_mesh_slots: dict[str, str] = {}
    if editable_preview:
        try:
            variant_analysis = inspect_livery_variant_part_analysis(skin_key, game_root)
            project["variantPolicy"] = _json_ready(variant_analysis.get("variantPolicy") or {})
            project["variantPolicyVersion"] = variant_analysis.get("variantPolicyVersion")
            project["surfacePolicies"] = _json_ready(variant_analysis.get("surfacePolicies") or [])
            result["variantAnalysis"] = _json_ready(variant_analysis)
            variant_classification = str(variant_analysis.get("variantClassification") or "")
            shared_additive_variant = variant_classification in {"shared_additive_parts", "shared_additive_parts_variant_ao"}
            variant_ao_additive = variant_classification == "shared_additive_parts_variant_ao"
            full_variant_model = variant_classification == "full_variant_model"
            variant_mesh_slots = {
                norm_slashes(str(row.get("relPath") or "")).lower(): str(row.get("slot") or "")
                for row in (variant_analysis.get("variant") or {}).get("meshRefs", []) or []
                if isinstance(row, dict) and str(row.get("relPath") or "").strip()
            }
            variant_source = str((variant_analysis.get("variant") or {}).get("compatiblePartSource") or "")
            compatible_refs = [
                str(row.get("compatiblePartRel") or "")
                for row in (variant_analysis.get("variant") or {}).get("compatibleParts", []) or []
                if isinstance(row, dict) and str(row.get("compatiblePartRel") or "").strip()
            ]
            allowed_mesh_refs = {
                norm_slashes(str(ref or "")).lower()
                for ref in (variant_analysis.get("variant") or {}).get("liveryRelevantMeshRefs", []) or []
                if str(ref or "").strip()
            }
            if shared_additive_variant and variant_source == "actor-static":
                variant_parts = _resolve_livery_variant_part_meshes(entry, game_root, compatible_refs=compatible_refs)
                if allowed_mesh_refs:
                    variant_parts["meshRefs"] = [
                        row for row in variant_parts.get("meshRefs", []) or []
                        if norm_slashes(str(row.get("relPath") or "")).lower() in allowed_mesh_refs
                    ]
                    variant_parts["skinMeshRefs"] = [
                        row for row in variant_parts.get("skinMeshRefs", []) or []
                        if norm_slashes(str(row.get("relPath") or "")).lower() in allowed_mesh_refs
                    ]
                    variant_parts["hideExteriorModel"] = False
            elif variant_classification in {"shared_additive_parts", "shared_additive_parts_variant_ao", "full_variant_model", "hybrid_replacement_parts"} and compatible_refs:
                variant_parts = _resolve_livery_variant_part_meshes(entry, game_root, compatible_refs=compatible_refs)
                if full_variant_model:
                    if allowed_mesh_refs:
                        variant_parts["meshRefs"] = [
                            row for row in variant_parts.get("meshRefs", []) or []
                            if norm_slashes(str(row.get("relPath") or "")).lower() in allowed_mesh_refs
                        ]
                        variant_parts["skinMeshRefs"] = [
                            row for row in variant_parts.get("skinMeshRefs", []) or []
                            if norm_slashes(str(row.get("relPath") or "")).lower() in allowed_mesh_refs
                        ]
                    if variant_parts.get("skinMeshRefs"):
                        variant_parts["hideExteriorModel"] = True
            else:
                variant_parts = {"compatibleParts": [], "meshRefs": [], "skinMeshRefs": [], "hideExteriorModel": False, "warnings": []}
            if variant_parts.get("meshRefs"):
                hybrid_replacement_variant = variant_classification == "hybrid_replacement_parts"
                replacement_skin_refs = [
                    row for row in variant_parts.get("skinMeshRefs", []) or []
                    if isinstance(row, dict) and row.get("replacesExterior")
                ]
                if hybrid_replacement_variant and replacement_skin_refs:
                    variant_parts["hideExteriorModel"] = False
                    result["hybridReplacementPreview"] = {
                        "hideExteriorModel": False,
                        "replacementMeshRefs": [row.get("relPath") for row in replacement_skin_refs if row.get("relPath")],
                    }
                project["hideExteriorModel"] = bool(variant_parts.get("hideExteriorModel"))
                project["variantPartPreview"] = _json_ready(variant_parts)
                result["hideExteriorModel"] = bool(variant_parts.get("hideExteriorModel"))
                result["variantPartPreview"] = _json_ready(variant_parts)
                _sync_livery_preview_hidden_materials(
                    result.get("projectPath") or project.get("projectPath") or "",
                    hidden_preview_materials,
                    hidden_exterior_materials,
                )
            else:
                project.pop("hideExteriorModel", None)
                project.pop("variantPartPreview", None)
                project.pop("hiddenExteriorMaterialNames", None)
                project.pop("hiddenExteriorComponentMaterials", None)
                result.pop("hideExteriorModel", None)
                result.pop("variantPartPreview", None)
                result.pop("hybridReplacementPreview", None)
            if (full_variant_model or variant_ao_additive) and project_folder:
                ao_sync = _sync_livery_preview_active_ao(
                    entry,
                    game_root,
                    skin_folder,
                    project_folder,
                    result.get("projectPath") or project.get("projectPath") or "",
                    project,
                    local_only=local_only,
                )
                result["variantAoSync"] = ao_sync
                if ao_sync.get("reason") and ao_sync.get("status") == "skipped" and ao_sync.get("reason") not in {"target current"}:
                    warnings.append(f"Variant AO skipped: {ao_sync.get('reason')}")
        except Exception as exc:
            warnings.append(f"Variant part probe failed: {exc}")
            variant_parts = {"compatibleParts": [], "meshRefs": [], "skinMeshRefs": [], "warnings": [str(exc)]}
    for warning in variant_parts.get("warnings", []) or []:
        warnings.append(str(warning))
    if editable_preview and project_folder and exterior_mesh.exists():
        try:
            mesh_uv = inspect_mesh_uv_view(exterior_mesh, game_root)
            variant_skin_mesh_uvs: list[dict[str, Any]] = []
            replacement_like_variant_skin_mesh_uvs: list[dict[str, Any]] = []
            for mesh_ref in variant_parts.get("skinMeshRefs", []) or []:
                mesh_path = Path(str(mesh_ref.get("path") or ""))
                if not mesh_path.exists():
                    continue
                try:
                    mesh_uv_row = inspect_mesh_uv_view(mesh_path, game_root)
                    rel_key = norm_slashes(str(mesh_ref.get("relPath") or "")).lower()
                    slot = variant_mesh_slots.get(rel_key) or _variant_analysis_part_slot(
                        str(mesh_ref.get("compatiblePartRel") or ""),
                        str(mesh_ref.get("carPartRel") or ""),
                    )
                    mesh_uv_row["variantPartSlot"] = slot
                    mesh_uv_row["variantPartRelPath"] = str(mesh_ref.get("relPath") or "")
                    variant_skin_mesh_uvs.append(mesh_uv_row)
                    if _variant_analysis_is_uv_replacement_like_slot(slot):
                        replacement_like_variant_skin_mesh_uvs.append(mesh_uv_row)
                except Exception as exc:
                    warnings.append(f"Variant part UV skipped: {mesh_ref.get('relPath') or mesh_path}: {exc}")
            uv_path = project_folder / uv_templates.UV_TEMPLATE_NAME
            if full_variant_model and variant_skin_mesh_uvs:
                uv_result = uv_templates.write_ext_skin_uv_template_multi(
                    variant_skin_mesh_uvs,
                    uv_path,
                    overwrite=True,
                )
                uv_result["variantPartMeshCount"] = len(variant_skin_mesh_uvs)
                uv_result["variantPolicy"] = "variant_model_set"
            elif shared_additive_variant and variant_skin_mesh_uvs:
                uv_result = uv_templates.write_ext_skin_uv_template(mesh_uv, uv_path, overwrite=True)
                uv_result["variantPartMeshCount"] = len(variant_skin_mesh_uvs)
                if variant_ao_additive and replacement_like_variant_skin_mesh_uvs:
                    uv_result["variantPolicy"] = "base_plus_replacement_parts"
                    uv_result["replacementLikeVariantPartMeshCount"] = len(replacement_like_variant_skin_mesh_uvs)
                    _set_variant_analysis_uv_policy(variant_analysis, "base_plus_replacement_parts")
                    project["variantPolicy"] = _json_ready(variant_analysis.get("variantPolicy") or {})
                    project["surfacePolicies"] = _json_ready(variant_analysis.get("surfacePolicies") or [])
                    result["variantAnalysis"] = _json_ready(variant_analysis)
                else:
                    uv_result["variantPolicy"] = "base_plus_shared_parts"
            else:
                uv_result = uv_templates.write_ext_skin_uv_template(mesh_uv, uv_path, overwrite=bool(variant_skin_mesh_uvs))
            uv_templates.update_preview_project_uv_template(result.get("projectPath") or project.get("projectPath") or "", project_folder, uv_result)
            result["uvTemplate"] = uv_result
            project["uvTemplateName"] = uv_templates.UV_TEMPLATE_NAME
            project["uvTemplatePath"] = str(uv_path)
            project["uvTemplate"] = uv_result
            if uv_result.get("reason") and uv_result.get("reason") != "exists":
                warnings.append(f"UV template skipped: {uv_result.get('reason')}")

            if variant_skin_mesh_uvs:
                parts_uv_path = project_folder / uv_templates.PARTS_UV_TEMPLATE_NAME
                parts_uv_result = uv_templates.write_ext_skin_parts_uv_template(
                    variant_skin_mesh_uvs,
                    parts_uv_path,
                    overwrite=True,
                )
                parts_uv_result["variantPartMeshCount"] = len(variant_skin_mesh_uvs)
                uv_templates.update_preview_project_parts_uv_template(
                    result.get("projectPath") or project.get("projectPath") or "",
                    project_folder,
                    parts_uv_result,
                )
                result["partsUvTemplate"] = parts_uv_result
                project["partsUvTemplateName"] = uv_templates.PARTS_UV_TEMPLATE_NAME
                project["partsUvTemplatePath"] = str(parts_uv_path)
                project["partsUvTemplate"] = parts_uv_result
                if parts_uv_result.get("reason") and parts_uv_result.get("reason") != "exists":
                    warnings.append(f"Parts UV template skipped: {parts_uv_result.get('reason')}")
            else:
                _clear_livery_preview_project_parts_uv(
                    result.get("projectPath") or project.get("projectPath") or "",
                    project,
                )

            _clear_livery_preview_project_variant_uv(
                result.get("projectPath") or project.get("projectPath") or "",
                project,
            )

            banner_size = _livery_project_banner_uv_size(project, game_root, entry, skin_folder, local_only=local_only)
            banner_uv_path = project_folder / uv_templates.BANNER_UV_TEMPLATE_NAME
            banner_uv_result = uv_templates.write_ext_banner_uv_template(
                mesh_uv,
                banner_uv_path,
                width=int(banner_size.get("width") or 4096),
                height=int(banner_size.get("height") or 1024),
                overwrite=False,
            )
            banner_uv_result["sizeSource"] = banner_size.get("source") or ""
            banner_uv_result["sizeSourcePath"] = banner_size.get("path") or ""
            uv_templates.update_preview_project_banner_uv_template(
                result.get("projectPath") or project.get("projectPath") or "",
                project_folder,
                banner_uv_result,
            )
            result["bannerUvTemplate"] = banner_uv_result
            project["bannerUvTemplateName"] = uv_templates.BANNER_UV_TEMPLATE_NAME
            project["bannerUvTemplatePath"] = str(banner_uv_path)
            project["bannerUvTemplate"] = banner_uv_result
            if (
                banner_uv_result.get("reason")
                and banner_uv_result.get("reason") != "exists"
                and banner_size.get("source") != "fallback"
            ):
                warnings.append(f"Banner UV template skipped: {banner_uv_result.get('reason')}")

        except Exception as exc:
            warnings.append(f"UV template generation failed: {exc}")
    elif editable_preview:
        warnings.append("UV template skipped: exterior mesh was not found.")
    if editable_preview and project_folder:
        try:
            readme_path = write_livery_project_readme(entry, project_folder)
            if readme_path:
                project["readmePath"] = str(readme_path)
                result["projectReadme"] = str(readme_path)
        except Exception as exc:
            warnings.append(f"Project README skipped: {exc}")
    if editable_preview:
        _update_livery_preview_project_variant_metadata(
            result.get("projectPath") or project.get("projectPath") or "",
            variant_parts,
            variant_analysis,
        )
    return result


def _livery_project_folder_for_entry(entry: dict[str, Any]) -> Path:
    car_model = preview3d.safe_segment(entry.get("car_model"), "car")
    skin_id = preview3d.safe_segment(entry.get("skin_id") or entry.get("display_name"), "skin")
    folder = get_livery_projects_root() / car_model / skin_id
    if not _is_official_external_my_skin_entry(entry):
        return folder
    if official_external_workflow.role_for_entry(entry) != official_external_workflow.ROLE_PROJECT:
        return folder
    if folder.exists() and folder.is_dir():
        return folder
    local_root = Path(str(entry.get("local_root") or "")).expanduser()
    if not local_root.exists() or not local_root.is_dir():
        return folder
    if (local_root / official_external_projects.PROJECT_MANIFEST).is_file() or (local_root / official_external_liveries.EXTERNAL_LIVERY_JSON).is_file():
        return local_root
    return folder


PROJECT_ASSET_REQUIRED_STEMS = ("ext_skin_c", "ext_skin_cc", "ext_skin_cr", "ext_skin_d", "ext_skin_m", "ext_skin_r")
PROJECT_ASSET_FALLBACK_REQUIRED_STEMS = tuple(stem for stem in PROJECT_ASSET_REQUIRED_STEMS if stem != "ext_skin_d")


def _livery_preview_project_path_for_entry(entry: dict[str, Any]) -> Path:
    return _livery_project_folder_for_entry(entry) / "preview_project.json"


def _read_livery_preview_project_for_entry(entry: dict[str, Any]) -> dict[str, Any]:
    path = _livery_preview_project_path_for_entry(entry)
    if not path.exists() or not path.is_file():
        return {}
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}
    return data if isinstance(data, dict) else {}


def _project_asset_path(project_folder: Path, raw_path: Any, fallback_name: str) -> Path:
    text = str(raw_path or "").strip()
    if text:
        path = Path(text)
        return path if path.is_absolute() else project_folder / path
    return project_folder / fallback_name


def _livery_project_required_texture_rows(entry: dict[str, Any], project_folder: Path) -> list[dict[str, Any]]:
    project = _read_livery_preview_project_for_entry(entry)
    maps = project.get("liveMaterialTextureMaps") if isinstance(project.get("liveMaterialTextureMaps"), list) else []
    rows: list[dict[str, Any]] = []
    seen: set[str] = set()
    if maps:
        for item in maps:
            if not isinstance(item, dict):
                continue
            stem = str(item.get("stem") or "").strip().lower()
            if stem not in PROJECT_ASSET_REQUIRED_STEMS or stem in seen:
                continue
            if bool(item.get("optional")):
                continue
            meta = _ext_skin_project_texture_meta(stem)
            rows.append({
                "kind": "projectTexture",
                "stem": stem,
                "label": str(item.get("label") or meta["label"]),
                "filename": meta["png"],
                "path": str(_project_asset_path(project_folder, item.get("path"), meta["png"])),
            })
            seen.add(stem)
    if not rows:
        for stem in PROJECT_ASSET_FALLBACK_REQUIRED_STEMS:
            meta = _ext_skin_project_texture_meta(stem)
            rows.append({
                "kind": "projectTexture",
                "stem": stem,
                "label": meta["label"],
                "filename": meta["png"],
                "path": str(project_folder / meta["png"]),
            })
    return rows


def inspect_my_skin_project_assets(entry: dict[str, Any], policy: dict[str, Any] | None = None) -> dict[str, Any]:
    skin_policy_row = policy if isinstance(policy, dict) else skin_policy.policy_for_skin(entry)
    project_folder = _livery_project_folder_for_entry(entry)
    enabled = bool(skin_policy_row.get("editable")) or official_to_advanced_import.uses_locked_project_workspace(entry)
    critical: list[dict[str, Any]] = []
    warnings: list[dict[str, Any]] = []
    if not enabled:
        return {
            "enabled": False,
            "projectFolder": str(project_folder),
            "critical": [],
            "criticalCount": 0,
            "warnings": [],
            "warningCount": 0,
        }
    if _is_official_external_my_skin_entry(entry):
        external_json = project_folder / official_external_liveries.EXTERNAL_LIVERY_JSON
        if not external_json.exists() or not external_json.is_file():
            critical.append({
                "kind": "officialExternalManifest",
                "label": "Official external_livery.json",
                "filename": official_external_liveries.EXTERNAL_LIVERY_JSON,
                "path": str(external_json),
                "exists": False,
                "message": f"Official external project manifest missing: {official_external_liveries.EXTERNAL_LIVERY_JSON}",
            })
        return {
            "enabled": True,
            "projectFolder": str(project_folder),
            "critical": _json_ready(critical),
            "criticalCount": len(critical),
            "warnings": [],
            "warningCount": 0,
        }
    for row in _livery_project_required_texture_rows(entry, project_folder):
        path = Path(str(row.get("path") or ""))
        if path.exists() and path.is_file():
            continue
        filename = str(row.get("filename") or path.name or row.get("label") or "texture").strip()
        critical.append({
            **row,
            "path": str(path),
            "exists": False,
            "message": f"Required LiveryProject texture missing: {filename}",
        })
    thumbnail = project_folder / SKIN_THUMBNAIL_FILENAME
    if not thumbnail.exists() or not thumbnail.is_file():
        warnings.append({
            "kind": "projectThumbnail",
            "label": "Livery thumbnail",
            "filename": SKIN_THUMBNAIL_FILENAME,
            "path": str(thumbnail),
            "exists": False,
            "message": f"LiveryProject thumbnail missing: {SKIN_THUMBNAIL_FILENAME}",
        })
    return {
        "enabled": True,
        "projectFolder": str(project_folder),
        "critical": _json_ready(critical),
        "criticalCount": len(critical),
        "warnings": _json_ready(warnings),
        "warningCount": len(warnings),
    }


def _ensure_road_plate_project_asset_for_entry(entry: dict[str, Any], game_root: Path) -> dict[str, Any]:
    project_folder = _livery_project_folder_for_entry(entry)
    return road_plates.ensure_project_plate_png(
        entry,
        game_root,
        project_folder,
        texture_to_png=lambda texture, png, overwrite_png: convert_texture_to_png(
            texture,
            png,
            overwrite=overwrite_png,
            create_dds=False,
        ),
    )


def _road_plate_project_png_path_for_entry(entry: dict[str, Any]) -> Path:
    return _livery_project_folder_for_entry(entry) / road_plates.PROJECT_PLATE_PNG


def _road_plate_local_skin_png_path_for_entry(entry: dict[str, Any]) -> Path | None:
    local_root = _entry_local_root(entry)
    files = entry.get("files") if isinstance(entry.get("files"), dict) else {}
    skin_folder_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    if local_root is None or not skin_folder_rel:
        return None
    return local_root / Path(*split_content_path(skin_folder_rel)) / road_plates.PROJECT_PLATE_PNG


def _road_plate_expected_generated_rels_for_entry(entry: dict[str, Any]) -> dict[str, str]:
    files = entry.get("files") if isinstance(entry.get("files"), dict) else {}
    skin_folder_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    car_content_path = normalize_skin_asset_rel_path(str(entry.get("car_content_path") or ""))
    skin_id = str(entry.get("skin_id") or entry.get("display_name") or "skin").strip()
    if not skin_folder_rel or not car_content_path:
        return {}
    clean_id = road_plates._clean_part_id(skin_id)
    texture_rel = norm_slashes(f"{skin_folder_rel}\\plate_r.texture")
    return {
        "textureRel": texture_rel,
        "mipsRel": norm_slashes(f"{skin_folder_rel}\\plate_r.texturemips"),
        "materialRel": norm_slashes(f"{skin_folder_rel}\\plate_r.material"),
        "meshRel": norm_slashes(f"{skin_folder_rel}\\plate_r.mesh"),
        "carPartRel": norm_slashes(f"{car_content_path}\\presets\\parts\\liverylab_{clean_id}_plate_r.carpart"),
        "compatiblePartRel": norm_slashes(f"{car_content_path}\\presets\\parts\\liverylab_{clean_id}_plate_r.compatiblepart"),
    }


def _road_plate_generated_rels_for_entry(entry: dict[str, Any]) -> dict[str, str]:
    road_plate = entry.get("road_plate") if isinstance(entry.get("road_plate"), dict) else {}
    expected = _road_plate_expected_generated_rels_for_entry(entry)
    result: dict[str, str] = {}
    for key in ("textureRel", "mipsRel", "materialRel", "meshRel", "carPartRel", "compatiblePartRel"):
        value = str(road_plate.get(key) or expected.get(key) or "").strip()
        if not value and key == "mipsRel":
            texture_rel = str(road_plate.get("textureRel") or expected.get("textureRel") or "").strip()
            if texture_rel.lower().endswith(".texture"):
                value = texture_rel[:-len(".texture")] + ".texturemips"
        if value:
            result[key] = normalize_skin_asset_rel_path(value)
    return result


def _road_plate_generated_assets_state_for_entry(entry: dict[str, Any]) -> dict[str, Any]:
    policy = skin_policy.policy_for_skin(entry)
    root = _entry_local_root(entry) if policy.get("readOnly") else None
    if root is None and not policy.get("readOnly"):
        game_root_raw = str(entry.get("game_root") or "").strip()
        root = normalize_game_root(game_root_raw) if game_root_raw else None
    rels = _road_plate_generated_rels_for_entry(entry)
    rows: dict[str, dict[str, Any]] = {}
    missing: list[str] = []
    present: list[str] = []
    if root is None:
        return {"root": "", "rels": rels, "assets": rows, "missing": list(rels), "present": [], "coreComplete": False}
    for key, rel in rels.items():
        path = root / Path(*split_content_path(rel))
        exists = path.exists() and path.is_file()
        rows[key] = {"rel": rel, "path": str(path), "exists": exists}
        (present if exists else missing).append(key)
    core_keys = ("textureRel", "mipsRel", "materialRel", "meshRel")
    core_complete = all(rows.get(key, {}).get("exists") for key in core_keys)
    return {
        "root": str(root),
        "rels": rels,
        "assets": rows,
        "missing": missing,
        "present": present,
        "coreComplete": core_complete,
    }


def _road_plate_effective_png_path_for_entry(entry: dict[str, Any]) -> Path:
    project_png = _road_plate_project_png_path_for_entry(entry)
    if project_png.exists():
        return project_png
    policy = skin_policy.policy_for_skin(entry)
    local_png = _road_plate_local_skin_png_path_for_entry(entry) if policy.get("readOnly") else None
    if local_png is not None:
        return local_png
    return project_png


def _road_plate_png_location_for_entry(entry: dict[str, Any], png: Path | None = None) -> str:
    if png is None:
        png = _road_plate_effective_png_path_for_entry(entry)
    project_png = _road_plate_project_png_path_for_entry(entry)
    try:
        if png.resolve() == project_png.resolve():
            return "project"
    except Exception:
        if str(png) == str(project_png):
            return "project"
    local_png = _road_plate_local_skin_png_path_for_entry(entry)
    if local_png is not None:
        try:
            if png.resolve() == local_png.resolve():
                return "local"
        except Exception:
            if str(png) == str(local_png):
                return "local"
    return "missing"


def _file_sha256(path: Path | None) -> str:
    if path is None or not path.exists() or not path.is_file():
        return ""
    digest = hashlib.sha256()
    with path.open("rb") as handle:
        for chunk in iter(lambda: handle.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def _road_plate_source(entry: dict[str, Any]) -> str:
    road_plate = entry.get("road_plate") if isinstance(entry.get("road_plate"), dict) else {}
    source = str(road_plate.get("source") or road_plate.get("selection") or "").strip().lower()
    if source in {"custom", "default"}:
        return source
    if any(road_plate.get(key) for key in ("compatiblePartRel", "carPartRel", "meshRel", "materialRel", "textureRel")):
        return "custom"
    return "default"


def _inspect_road_plate_support_for_entry(entry: dict[str, Any], game_root: Path) -> dict[str, Any]:
    files = entry.get("files") if isinstance(entry.get("files"), dict) else {}
    actor_rel = road_plates.normalize_game_rel_path(str(files.get("actor") or ""))
    actor_path = road_plates.resolve_content_path(game_root, actor_rel) if actor_rel else None
    return road_plates.inspect_actor_road_plate(game_root, actor_path)


def _ensure_road_plate_metadata_for_entry(entry: dict[str, Any], game_root: Path) -> dict[str, Any]:
    support = _inspect_road_plate_support_for_entry(entry, game_root)
    current = dict(entry.get("road_plate") or {}) if isinstance(entry.get("road_plate"), dict) else {}
    if not support.get("supported"):
        if current:
            entry.pop("road_plate", None)
            return {"changed": True, "support": support}
        return {"changed": False, "support": support}
    source = _road_plate_source(entry)
    project_png = _road_plate_project_png_path_for_entry(entry)
    next_meta = dict(current)
    next_meta.update(
        {
            "supported": True,
            "kind": "road_rear",
            "label": road_plates.PLATE_LABEL,
            "source": source,
            "projectPng": road_plates.PROJECT_PLATE_PNG if project_png.exists() else str(next_meta.get("projectPng") or ""),
            "sourceTextureRel": support.get("textureRel") or next_meta.get("sourceTextureRel") or "",
            "sourceMaterialRel": support.get("materialRel") or next_meta.get("sourceMaterialRel") or "",
            "sourceMeshRel": support.get("meshRel") or next_meta.get("sourceMeshRel") or "",
            "plateFamily": support.get("plateFamily") or next_meta.get("plateFamily") or "",
            "plateSide": support.get("plateSide") or next_meta.get("plateSide") or "",
            "textureWidth": support.get("textureWidth") or next_meta.get("textureWidth") or 0,
            "textureHeight": support.get("textureHeight") or next_meta.get("textureHeight") or 0,
            "textureMips": support.get("textureMips") or next_meta.get("textureMips") or 0,
        }
    )
    if source != "custom":
        next_meta.pop("lastSyncedProjectPngHash", None)
    changed = _json_ready(current) != _json_ready(next_meta)
    entry["road_plate"] = _json_ready(next_meta)
    return {"changed": changed, "support": support, "roadPlate": entry["road_plate"]}


def _road_plate_game_sync_state_for_entry(entry: dict[str, Any]) -> dict[str, Any]:
    road_plate = entry.get("road_plate") if isinstance(entry.get("road_plate"), dict) else {}
    if str(road_plate.get("source") or "").strip().lower() != "custom":
        return {}
    policy = skin_policy.policy_for_skin(entry)
    if policy.get("readOnly"):
        assets = _road_plate_generated_assets_state_for_entry(entry)
        if assets.get("coreComplete"):
            return {}
        return _my_skin_game_sync_state("Imported custom rear plate assets are incomplete", "road_plate")
    plate_png = _road_plate_effective_png_path_for_entry(entry)
    if not plate_png.exists():
        return _my_skin_game_sync_state("Custom rear plate image missing", "road_plate")
    current_hash = _file_sha256(plate_png)
    synced_hash = str(road_plate.get("lastSyncedProjectPngHash") or "").strip()
    if current_hash and not synced_hash:
        return _my_skin_game_sync_state("Custom rear plate needs Update in Game", "road_plate")
    if current_hash and synced_hash and current_hash != synced_hash:
        return _my_skin_game_sync_state("Custom rear plate image changed", "road_plate")
    return {}


def _road_plate_custom_source_missing(entry: dict[str, Any]) -> bool:
    road_plate = entry.get("road_plate") if isinstance(entry.get("road_plate"), dict) else {}
    if str(road_plate.get("source") or "").strip().lower() != "custom":
        return False
    if skin_policy.policy_for_skin(entry).get("readOnly"):
        return False
    return not _road_plate_effective_png_path_for_entry(entry).exists()


def _road_plate_mechanical_paths_for_entry(
    entry: dict[str, Any],
    game_root: Path,
    mechanical_rels: Iterable[Any] | None = None,
) -> list[Path]:
    files = entry.get("files") if isinstance(entry.get("files"), dict) else {}
    refs: list[Any] = []
    refs.extend(mechanical_rels or [])
    for _kind, rel in _file_rel_values(files, {"mechanical_preset", "mechanical_presets"}):
        refs.append(rel)
    for _kind, rel in _file_rel_values(files, {"carfinalstate", "carfinalstates"}):
        finalstate_path = _disk_from_game_rel(game_root, rel)
        if not finalstate_path or not finalstate_path.exists() or not finalstate_path.is_file():
            continue
        try:
            info = extract_carfinalstate_info(finalstate_path.read_bytes())
        except Exception:
            continue
        refs.append(info.get("mechanical_preset") or "")

    paths: list[Path] = []
    seen: set[str] = set()
    for ref in refs:
        rel_norm = normalize_game_rel_path(str(ref or ""))
        if not rel_norm or not rel_norm.lower().endswith(".mechanicalcarpreset"):
            continue
        path = _disk_from_game_rel(game_root, rel_norm)
        if not path or not path.exists() or not path.is_file():
            continue
        key = str(path.resolve()).lower()
        if key in seen:
            continue
        seen.add(key)
        paths.append(path)
    return paths


def _road_plate_managed_compatible_rels_for_car(entry: dict[str, Any]) -> list[str]:
    car_content_path = normalize_skin_asset_rel_path(str(entry.get("car_content_path") or ""))
    car_model = str(entry.get("car_model") or "").strip().lower()
    refs: list[str] = []
    seen: set[str] = set()
    for item in load_my_skins():
        item_car_path = normalize_skin_asset_rel_path(str(item.get("car_content_path") or ""))
        item_car_model = str(item.get("car_model") or "").strip().lower()
        if car_content_path:
            same_car = item_car_path.lower() == car_content_path.lower()
        else:
            same_car = bool(car_model and item_car_model == car_model)
        if not same_car:
            continue
        road_plate = item.get("road_plate") if isinstance(item.get("road_plate"), dict) else {}
        if str(road_plate.get("source") or "").strip().lower() != "custom":
            continue
        rel = normalize_skin_asset_rel_path(str(road_plate.get("compatiblePartRel") or ""))
        if not rel:
            rel = normalize_skin_asset_rel_path(_road_plate_expected_generated_rels_for_entry(item).get("compatiblePartRel") or "")
        key = rel.lower()
        if not key or key in seen:
            continue
        seen.add(key)
        refs.append(rel)
    return refs


def _sync_road_plate_for_entry(
    entry: dict[str, Any],
    game_root: Path,
    visual_rel: str,
    writer: GameFileWriter,
    mechanical_rels: Iterable[Any] | None = None,
) -> dict[str, Any]:
    road_plate = entry.get("road_plate") if isinstance(entry.get("road_plate"), dict) else {}
    source = str(road_plate.get("source") or "").strip().lower()
    if source != "custom":
        return _cleanup_road_plate_for_entry(entry, game_root, visual_rel, writer)
    project_folder = _livery_project_folder_for_entry(entry)
    plate_png = _road_plate_effective_png_path_for_entry(entry)
    local_root = _entry_local_root(entry)
    files = entry.get("files") if isinstance(entry.get("files"), dict) else {}
    savedcars: list[Path] = []
    savedcar_errors: list[str] = []
    savedcar_seen: set[str] = set()
    scan_refs = [
        visual_rel,
        str(files.get("visual_preset") or ""),
        str(files.get("cardesign") or ""),
        str(files.get("design") or ""),
        str(files.get("skin_folder") or ""),
    ]
    scan_index = _build_savedcars_scan_index()
    for ref in scan_refs:
        if not str(ref or "").strip():
            continue
        hits, errors = _scan_savedcars_for_path(str(ref), scan_index=scan_index)
        savedcar_errors.extend(errors)
        for path in hits:
            key = str(path.resolve()).lower()
            if key in savedcar_seen:
                continue
            savedcar_seen.add(key)
            savedcars.append(path)
    result = road_plates.sync_entry_road_plate(
        entry,
        game_root,
        project_folder,
        writer,
        parse_texture_descriptor=parse_texture_descriptor,
        choose_auto_format=choose_auto_format,
        convert_image_to_dds_with_texconv=convert_image_to_dds_with_texconv,
        texturemips_bytes_from_dds=texturemips_bytes_from_dds,
        project_png_path=plate_png,
        local_root=local_root,
        savedcar_paths=savedcars,
        mechanical_paths=_road_plate_mechanical_paths_for_entry(entry, game_root, mechanical_rels),
        managed_compatible_rels=_road_plate_managed_compatible_rels_for_car(entry),
    )
    if savedcar_errors:
        result["savedcarErrors"] = savedcar_errors
    if not result.get("skipped"):
        road_plate = dict(entry.get("road_plate") or {}) if isinstance(entry.get("road_plate"), dict) else {}
        road_plate["source"] = "custom"
        road_plate["lastSyncedProjectPngHash"] = _file_sha256(plate_png)
        entry["road_plate"] = _json_ready(road_plate)
    return result


def _cleanup_road_plate_for_entry(
    entry: dict[str, Any],
    game_root: Path,
    visual_rel: str,
    writer: GameFileWriter,
    mechanical_rels: Iterable[Any] | None = None,
) -> dict[str, Any]:
    road_plate = entry.get("road_plate") if isinstance(entry.get("road_plate"), dict) else {}
    if not any(road_plate.get(key) for key in ("compatiblePartRel", "carPartRel", "meshRel", "materialRel", "textureRel")):
        expected_rels = _road_plate_expected_generated_rels_for_entry(entry)
        if not expected_rels:
            return {"changed": False, "skipped": "no generated road plate metadata"}
        road_plate = dict(road_plate)
        for key in ("compatiblePartRel", "carPartRel", "meshRel", "materialRel", "textureRel"):
            if expected_rels.get(key):
                road_plate[key] = expected_rels[key]
        entry["road_plate"] = _json_ready(road_plate)
    local_root = _entry_local_root(entry)
    files = entry.get("files") if isinstance(entry.get("files"), dict) else {}
    savedcars: list[Path] = []
    savedcar_errors: list[str] = []
    savedcar_seen: set[str] = set()
    scan_refs = [
        str(road_plate.get("compatiblePartRel") or ""),
        str(road_plate.get("carPartRel") or ""),
        visual_rel,
        str(files.get("visual_preset") or ""),
        str(files.get("cardesign") or ""),
        str(files.get("design") or ""),
        str(files.get("skin_folder") or ""),
    ]
    scan_index = _build_savedcars_scan_index()
    for ref in scan_refs:
        if not str(ref or "").strip():
            continue
        hits, errors = _scan_savedcars_for_path(str(ref), scan_index=scan_index)
        savedcar_errors.extend(errors)
        for path in hits:
            key = str(path.resolve()).lower()
            if key in savedcar_seen:
                continue
            savedcar_seen.add(key)
            savedcars.append(path)
    result = road_plates.cleanup_entry_road_plate(
        entry,
        game_root,
        writer,
        local_root=local_root,
        savedcar_paths=savedcars,
        mechanical_paths=_road_plate_mechanical_paths_for_entry(entry, game_root, mechanical_rels),
        managed_compatible_rels=_road_plate_managed_compatible_rels_for_car(entry),
    )
    if savedcar_errors:
        result["savedcarErrors"] = savedcar_errors
    return result


def _livery_project_readme_text(entry: dict[str, Any], project_folder: Path) -> str:
    car_model = str(entry.get("car_model") or "").strip() or "-"
    skin_id = str(entry.get("skin_id") or "").strip() or "-"
    display_name = str(entry.get("display_name") or entry.get("name") or "").strip() or "-"
    return generated_readmes.advanced_project_readme(
        car_model,
        skin_id,
        display_name,
        road_plates.PROJECT_PLATE_PNG,
    )


def write_livery_project_readme(entry: dict[str, Any], project_folder: Path | None = None) -> Path | None:
    folder = project_folder or _livery_project_folder_for_entry(entry)
    if not folder:
        return None
    folder.mkdir(parents=True, exist_ok=True)
    readme = folder / LIVERY_PROJECT_README_FILENAME
    if readme.exists():
        existing = readme.read_text(encoding="utf-8", errors="ignore")
        if generated_readmes.is_managed_advanced_project_readme(existing):
            readme.write_text(_livery_project_readme_text(entry, folder), encoding="utf-8")
        return readme
    readme.write_text(_livery_project_readme_text(entry, folder), encoding="utf-8")
    return readme


def inspect_livery_project_for_skin(skin_key: str) -> dict[str, Any]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    folder = _livery_project_folder_for_entry(entry)
    files: list[dict[str, Any]] = []
    work_files: list[str] = []
    if folder.exists() and folder.is_dir():
        for path in sorted((row for row in folder.rglob("*") if row.is_file()), key=lambda row: str(row).lower()):
            rel = norm_slashes(str(path.relative_to(folder)))
            item = {
                "path": str(path),
                "relative": rel,
                "extension": path.suffix.lower(),
                "size": path.stat().st_size,
            }
            files.append(item)
            if path.suffix.lower() in LIVERY_PROJECT_WORK_EXTENSIONS:
                work_files.append(rel)
    return {
        "skinKey": skin_key,
        "projectFolder": folder,
        "exists": folder.exists() and folder.is_dir(),
        "fileCount": len(files),
        "workFileCount": len(work_files),
        "workFiles": work_files,
        "suggestMoveToTrash": bool(folder.exists() and folder.is_dir() and not work_files),
        "files": files[:200],
    }


def _preview_aux_model_cache_path(models_dir: Path, label: str, mesh_path: Path) -> Path:
    stem = preview3d.safe_segment(mesh_path.stem, "mesh")
    digest = hashlib.sha1(str(mesh_path).lower().encode("utf-8")).hexdigest()[:10]
    return models_dir / "wheels" / f"{label}_{stem}_{digest}.glb"


def _preview_variant_part_model_cache_path(models_dir: Path, label: str, mesh_path: Path) -> Path:
    stem = preview3d.safe_segment(mesh_path.stem, "mesh")
    digest = hashlib.sha1(str(mesh_path).lower().encode("utf-8")).hexdigest()[:10]
    return models_dir / "parts" / f"{label}_{stem}_{digest}.glb"


def _write_preview_aux_model(
    mesh_path: Path,
    glb_path: Path,
    root: Path,
    force: bool = False,
    mesh_info: dict[str, Any] | None = None,
) -> dict[str, Any]:
    meta_path = glb_path.with_suffix(".meta.json")
    mesh_stat = mesh_path.stat()
    source_mesh_rel = content_rel_from_disk(root, mesh_path)
    cache_valid = False
    meta: dict[str, Any] = {}
    if glb_path.exists() and meta_path.exists() and not force:
        try:
            meta = json.loads(meta_path.read_text(encoding="utf-8"))
        except Exception:
            meta = {}
        stored_source_rel = str(meta.get("sourceMeshRelPath") or "").strip()
        stored_source_path = str(meta.get("sourceMeshPath") or "").strip()
        cache_valid = (
            (stored_source_rel == str(source_mesh_rel or "") or stored_source_path == str(mesh_path))
            and int(meta.get("sourceMeshSize") or -1) == int(mesh_stat.st_size)
            and int(meta.get("sourceMeshMtimeMs") or -1) == int(mesh_stat.st_mtime * 1000)
            and int(meta.get("exporterVersion") or 0) == int(preview3d.GLB_EXPORTER_VERSION)
        )
    if cache_valid:
        return {
            "path": str(glb_path),
            "cached": True,
            "meshPath": str(mesh_path),
            "meshRelPath": meta.get("sourceMeshRelPath"),
            "meshName": meta.get("meshName"),
            "vertexCount": meta.get("vertexCount"),
            "triangleCount": meta.get("triangleCount"),
            "boundsMin": meta.get("boundsMin"),
            "boundsMax": meta.get("boundsMax"),
        }

    prepared_mesh_info = mesh_info if isinstance(mesh_info, dict) else inspect_mesh_readable(mesh_path, root=root)
    glb = preview3d.write_mesh_info_glb(prepared_mesh_info, glb_path, str(prepared_mesh_info.get("material_name") or mesh_path.stem))
    meta = {
        **glb,
        "sourceMeshRelPath": source_mesh_rel,
        "sourceMeshSize": int(mesh_stat.st_size),
        "sourceMeshMtimeMs": int(mesh_stat.st_mtime * 1000),
        "updatedAt": datetime.now().isoformat(timespec="seconds"),
    }
    meta_path.parent.mkdir(parents=True, exist_ok=True)
    meta_path.write_text(json.dumps(meta, indent=2, ensure_ascii=False), encoding="utf-8")
    return {
        "path": str(glb_path),
        "cached": False,
        "meshPath": str(mesh_path),
        "meshRelPath": meta.get("sourceMeshRelPath"),
        "meshName": glb.get("meshName"),
        "vertexCount": glb.get("vertexCount"),
        "triangleCount": glb.get("triangleCount"),
        "boundsMin": glb.get("boundsMin"),
        "boundsMax": glb.get("boundsMax"),
    }


def _variant_part_slot_from_preview_ref(row: dict[str, Any]) -> str:
    slot = variant_parts.part_slot(
        str(row.get("compatiblePartRel") or ""),
        str(row.get("carPartRel") or ""),
    )
    if slot:
        return slot
    text = " ".join(
        str(row.get(key) or "")
        for key in ("mountName", "compatiblePartRel", "carPartRel", "relPath", "label")
    ).lower()
    if "diffusor" in text or "diffuser" in text:
        return "diffusor"
    if "front_bumper" in text or "fbumper" in text:
        return "front_bumper"
    if "hood" in text or "bonnet" in text:
        return "hood"
    if "rear_bumper" in text or "rbumper" in text:
        return "rear_bumper"
    if "wing" in text or "spoiler" in text:
        return "wing"
    if "body" in text:
        return "body"
    return ""


def _translated_bounds(row: dict[str, Any]) -> tuple[list[float], list[float]] | None:
    bounds_min = row.get("boundsMin") if isinstance(row.get("boundsMin"), list) else None
    bounds_max = row.get("boundsMax") if isinstance(row.get("boundsMax"), list) else None
    if not bounds_min or not bounds_max or len(bounds_min) < 3 or len(bounds_max) < 3:
        return None
    transform = row.get("transform") if isinstance(row.get("transform"), dict) else {}
    offset = [float(transform.get(axis) or 0.0) for axis in ("x", "y", "z")]
    return (
        [float(bounds_min[index]) + offset[index] for index in range(3)],
        [float(bounds_max[index]) + offset[index] for index in range(3)],
    )


def _bounds_inside(inner_min: list[float], inner_max: list[float], outer_min: list[float], outer_max: list[float], tolerance: float = 0.04) -> bool:
    return all(
        float(outer_min[index]) - tolerance <= float(inner_min[index])
        and float(inner_max[index]) <= float(outer_max[index]) + tolerance
        for index in range(3)
    )


def _variant_part_preview_mesh_refs(variant_part_preview: dict[str, Any]) -> list[dict[str, Any]]:
    if not isinstance(variant_part_preview, dict):
        return []
    refs: list[dict[str, Any]] = []
    seen: set[tuple[str, str, str, str, str]] = set()
    for key in ("meshRefs", "skinMeshRefs"):
        for row in variant_part_preview.get(key, []) or []:
            if not isinstance(row, dict):
                continue
            dedupe_key = (
                str(row.get("relPath") or ""),
                str(row.get("path") or ""),
                str(row.get("compatiblePartRel") or ""),
                str(row.get("carPartRel") or ""),
                str(row.get("label") or ""),
            )
            if dedupe_key in seen:
                continue
            seen.add(dedupe_key)
            refs.append(row)
    return refs


def _hybrid_hidden_exterior_component_materials(
    glb: dict[str, Any],
    variant_part_preview: dict[str, Any],
    *,
    allowed_slots: set[str] | None = None,
    include_body_shell: bool = True,
) -> list[str]:
    component_materials = [row for row in (glb.get("componentMaterials") or []) if isinstance(row, dict)]
    if not component_materials:
        return []
    paint_material = "EXT_SKIN"
    detail_wing_materials = {
        "EXT_DETAILS_CHROME",
        "EXT_DETAILS_PLASTIC",
        "EXT_DETAILS_PLASTIC_GLOSS",
        "EXT_DETAILS_RUBBER",
    }
    allowed_slots = set(allowed_slots or {"front_bumper", "hood", "wing", "body"})
    relevant_parts: list[tuple[str, list[float], list[float]]] = []
    for row in _variant_part_preview_mesh_refs(variant_part_preview):
        if not isinstance(row, dict) or row.get("previewRelevant") is False:
            continue
        slot = _variant_part_slot_from_preview_ref(row)
        if slot not in allowed_slots:
            continue
        translated = _translated_bounds(row)
        if not translated:
            continue
        relevant_parts.append((slot, translated[0], translated[1]))
    if not relevant_parts:
        return []
    hidden: set[str] = set()
    body_shell_active = include_body_shell and any(slot == "body" for slot, _part_min, _part_max in relevant_parts)

    def body_shell_component(component_min: list[float], component_max: list[float], triangle_count: int) -> bool:
        if triangle_count < 100:
            return False
        x_min, y_min, z_min = (float(component_min[index]) for index in range(3))
        x_max, y_max, z_max = (float(component_max[index]) for index in range(3))
        abs_x_min = min(abs(x_min), abs(x_max))
        abs_x_max = max(abs(x_min), abs(x_max))
        width = x_max - x_min
        height = y_max - y_min
        depth = z_max - z_min
        # Keep central rear bumper / tailgate-like pieces.
        if width > 1.0 and z_max < -1.85 and y_max < 0.85:
            return False
        # Keep large door-like side panels in the middle of the car.
        if abs_x_min > 0.70 and depth > 1.0 and height > 0.45 and -0.85 < z_min < -0.2 and z_max > 0.45:
            return False
        # Roof + rear-quarter shell and upper rear side surfaces.
        if width > 1.2 and y_max > 1.1 and z_min < -0.7 and z_max > -0.75 and z_max < 0.7:
            return True
        # Front fenders and front-side replacement strips.
        if abs_x_min > 0.60 and z_max > 0.45 and z_min < 1.95:
            return True
        # Rear quarter / side skirt pieces, excluding central rear bumper above.
        if abs_x_min > 0.70 and z_min < -0.35 and z_max < 0.7:
            return True
        return False

    for component in component_materials:
        source_material = str(component.get("sourceMaterial") or "").upper()
        if source_material != paint_material and source_material not in detail_wing_materials:
            continue
        triangle_count = int(component.get("triangleCount") or 0)
        minimum_triangles = 100 if source_material == paint_material else 8
        if triangle_count < minimum_triangles:
            continue
        component_min = component.get("boundsMin") if isinstance(component.get("boundsMin"), list) else None
        component_max = component.get("boundsMax") if isinstance(component.get("boundsMax"), list) else None
        if not component_min or not component_max or len(component_min) < 3 or len(component_max) < 3:
            continue
        if source_material in detail_wing_materials:
            part_matches = (
                (slot, part_min, part_max)
                for slot, part_min, part_max in relevant_parts
                if slot == "wing"
            )
        else:
            part_matches = (
                (slot, part_min, part_max)
                for slot, part_min, part_max in relevant_parts
                if slot != "body"
            )
        part_matches = list(part_matches)
        if any(_bounds_inside(component_min, component_max, part_min, part_max) for _slot, part_min, part_max in part_matches):
            material = str(component.get("material") or "").strip()
            if material:
                hidden.add(material)
            continue
        if source_material == paint_material and body_shell_active and body_shell_component(component_min, component_max, triangle_count):
            material = str(component.get("material") or "").strip()
            if material:
                hidden.add(material)
    return sorted(hidden)


def _variant_preview_has_component_hide_slots(variant_part_preview: dict[str, Any]) -> bool:
    for row in _variant_part_preview_mesh_refs(variant_part_preview):
        if not isinstance(row, dict) or row.get("previewRelevant") is False:
            continue
        slot = _variant_part_slot_from_preview_ref(row)
        if variant_parts.is_component_hide_replacement_slot(slot):
            return True
    return False


def _shared_additive_hidden_exterior_component_materials(glb: dict[str, Any], variant_part_preview: dict[str, Any]) -> list[str]:
    if not _variant_preview_has_component_hide_slots(variant_part_preview):
        return []
    return _hybrid_hidden_exterior_component_materials(
        glb,
        variant_part_preview,
        allowed_slots=set(variant_parts.COMPONENT_HIDE_REPLACEMENT_SLOTS),
        include_body_shell=False,
    )


def _apply_variant_hidden_exterior_materials(
    glb: dict[str, Any],
    project: dict[str, Any],
    variant_part_preview: dict[str, Any],
) -> list[str]:
    variant_classification = str((project.get("variantPolicy") or {}).get("classification") or "")
    if variant_classification not in {"hybrid_replacement_parts", "shared_additive_parts"} or not isinstance(variant_part_preview, dict):
        return []
    if variant_classification == "hybrid_replacement_parts":
        hidden_component_materials = _hybrid_hidden_exterior_component_materials(glb, variant_part_preview)
    else:
        hidden_component_materials = _shared_additive_hidden_exterior_component_materials(glb, variant_part_preview)
    if hidden_component_materials:
        project["hiddenExteriorMaterialNames"] = hidden_component_materials
        project["hiddenExteriorComponentMaterials"] = hidden_component_materials
        variant_part_preview["hiddenExteriorComponentMaterials"] = hidden_component_materials
        project["variantPartPreview"] = _json_ready(variant_part_preview)
    else:
        project.pop("hiddenExteriorMaterialNames", None)
        project.pop("hiddenExteriorComponentMaterials", None)
    return hidden_component_materials


def _resolve_preview_mesh_ref(game_root: Path, rel_text: str) -> tuple[str, Path, str]:
    mesh_path = resolve_content_path(game_root, rel_text)
    source_text = rel_text
    if mesh_path.exists() and mesh_path.suffix.lower() == ".tyremesh":
        try:
            refs = [
                text
                for _field, text in collect_proto_strings(mesh_path.read_bytes())
                if str(text or "").lower().endswith(".mesh") and str(text or "").lower().startswith("content\\")
            ]
        except Exception:
            refs = []
        if refs:
            rel_text = refs[0]
            mesh_path = resolve_content_path(game_root, rel_text)
    return rel_text, mesh_path, source_text


def _proto_float_at(data: bytes, path: str) -> float | None:
    parts = [int(part) for part in str(path).split(".") if part]
    current = data
    for index, number in enumerate(parts):
        try:
            fields = parse_fields(current)
        except Exception:
            return None
        field = next((row for row in fields if row.number == number), None)
        if field is None:
            return None
        if index == len(parts) - 1:
            if field.wire == 5 and len(field.value) == 4:
                return float(struct.unpack("<f", bytes(field.value))[0])
            return None
        if field.wire != 2:
            return None
        current = bytes(field.value)
    return None


_WHEEL_CALIBRATIONS_CACHE: dict[str, Any] | None = None


def _wheel_calibrations_path() -> Path:
    candidates: list[Path] = []
    frozen_root = getattr(sys, "_MEIPASS", None)
    if frozen_root:
        candidates.append(Path(frozen_root) / "liverylab_core" / "wheel_calibrations.json")
    module_root = Path(__file__).resolve().parent
    candidates.extend(
        [
            module_root / "liverylab_core" / "wheel_calibrations.json",
            module_root.parent / "liverylab_core" / "wheel_calibrations.json",
            Path.cwd() / "liverylab_core" / "wheel_calibrations.json",
        ]
    )
    for path in candidates:
        if path.exists():
            return path
    return candidates[0]


def _load_wheel_calibrations() -> dict[str, Any]:
    global _WHEEL_CALIBRATIONS_CACHE
    if _WHEEL_CALIBRATIONS_CACHE is not None:
        return _WHEEL_CALIBRATIONS_CACHE
    try:
        data = json.loads(_wheel_calibrations_path().read_text(encoding="utf-8"))
    except Exception:
        data = {}
    _WHEEL_CALIBRATIONS_CACHE = data if isinstance(data, dict) else {}
    return _WHEEL_CALIBRATIONS_CACHE


def _wheel_calibrations_source_mirror_path() -> Path | None:
    program_root = get_program_root()
    if program_root.name != "release":
        return None
    candidate = program_root.parent / "liverylab_core" / "wheel_calibrations.json"
    return candidate if candidate.exists() else None


def _save_wheel_calibration_file(path: Path, car_key: str, cleaned: dict[str, dict[str, float]]) -> Path | None:
    try:
        data = json.loads(path.read_text(encoding="utf-8")) if path.exists() else {}
    except Exception:
        data = {}
    if not isinstance(data, dict):
        data = {}
    data.setdefault("version", 1)
    data.setdefault("notes", "Per-car wheel preview calibration in meters. Values are applied on top of cardata-derived wheelbase/track placement.")
    cars = data.setdefault("cars", {})
    if not isinstance(cars, dict):
        cars = {}
        data["cars"] = cars
    car_data = cars.get(car_key)
    if not isinstance(car_data, dict):
        car_data = {}
    existing_overrides_raw = car_data.get("wheelOverrides")
    merged: dict[str, dict[str, float]] = {}
    if isinstance(existing_overrides_raw, dict):
        for label, values in existing_overrides_raw.items():
            if not isinstance(values, dict):
                continue
            row: dict[str, float] = {}
            for axis in ("x", "y", "z"):
                if axis not in values:
                    continue
                try:
                    row[axis] = round(float(values[axis]), 5)
                except (TypeError, ValueError):
                    continue
            if row:
                merged[str(label)] = row
    if cleaned:
        for label, values in cleaned.items():
            row = merged.setdefault(str(label), {})
            row.update(values)
        car_data["wheelOverrides"] = {label: values for label, values in merged.items() if values}
    elif not merged:
        car_data.pop("wheelOverrides", None)
    cars[car_key] = car_data
    path.parent.mkdir(parents=True, exist_ok=True)
    return safe_write_text(path, json.dumps(data, indent=2, ensure_ascii=False))


def _apply_wheel_calibration(layout: dict[str, Any], car_model: str) -> None:
    car_key = str(car_model or "").strip().lower()
    if not car_key:
        return
    cars = _load_wheel_calibrations().get("cars")
    if not isinstance(cars, dict):
        return
    calibration = cars.get(car_key)
    if not isinstance(calibration, dict):
        for key, value in cars.items():
            if not isinstance(value, dict):
                continue
            keys = [str(key).lower(), *[str(alias).lower() for alias in value.get("aliases", [])]]
            if car_key in keys:
                calibration = value
                break
    if not isinstance(calibration, dict):
        return

    numeric_keys = {
        "wheelbase",
        "frontTrack",
        "rearTrack",
        "wheelbaseAdjust",
        "frontTrackAdjust",
        "rearTrackAdjust",
        "wheelYOffset",
        "frontWheelYOffset",
        "rearWheelYOffset",
        "frontWheelZOffset",
        "rearWheelZOffset",
    }
    applied: dict[str, float] = {}
    for key in numeric_keys:
        if key not in calibration:
            continue
        try:
            value = float(calibration[key])
        except (TypeError, ValueError):
            continue
        layout[key] = value
        applied[key] = value
    overrides_raw = calibration.get("wheelOverrides")
    if isinstance(overrides_raw, dict):
        overrides: dict[str, dict[str, float]] = {}
        for label, values in overrides_raw.items():
            if not isinstance(values, dict):
                continue
            cleaned: dict[str, float] = {}
            for axis in ("x", "y", "z"):
                if axis not in values:
                    continue
                try:
                    cleaned[axis] = float(values[axis])
                except (TypeError, ValueError):
                    continue
            if cleaned:
                overrides[str(label)] = cleaned
        if overrides:
            layout["wheelOverrides"] = overrides
    if applied:
        layout["calibration"] = {
            "car": car_key,
            "source": str(_wheel_calibrations_path()),
            "values": applied,
        }
    if layout.get("wheelOverrides"):
        layout.setdefault("calibration", {"car": car_key, "source": str(_wheel_calibrations_path()), "values": {}})
        layout["calibration"]["wheelOverrides"] = layout["wheelOverrides"]


def save_wheel_preview_calibration(car_model: str, wheel_overrides: dict[str, Any]) -> dict[str, Any]:
    if not is_debug_tool_enabled("wheelCalibration"):
        raise PermissionError(f"Wheel calibration saving requires debug mode. Start LiveryLab with {DEBUG_TOOLS_ARG} to enable it.")
    car_key = str(car_model or "").strip().lower()
    if not car_key:
        raise ValueError("Car model is required")
    cleaned: dict[str, dict[str, float]] = {}
    for label, values in (wheel_overrides or {}).items():
        if not isinstance(values, dict):
            continue
        row: dict[str, float] = {}
        for axis in ("x", "y", "z"):
            if axis not in values:
                continue
            try:
                value = float(values[axis])
            except (TypeError, ValueError):
                continue
            row[axis] = round(value, 5)
        if row:
            cleaned[str(label)] = row

    path = _wheel_calibrations_path()
    backup = _save_wheel_calibration_file(path, car_key, cleaned)
    mirror_path = _wheel_calibrations_source_mirror_path()
    mirror_backup = None
    if mirror_path and mirror_path.resolve() != path.resolve():
        mirror_backup = _save_wheel_calibration_file(mirror_path, car_key, cleaned)
    global _WHEEL_CALIBRATIONS_CACHE
    _WHEEL_CALIBRATIONS_CACHE = None
    return {
        "carModel": car_key,
        "path": str(path),
        "backup": str(backup) if backup else "",
        "sourceMirrorPath": str(mirror_path) if mirror_path else "",
        "sourceMirrorBackup": str(mirror_backup) if mirror_backup else "",
        "wheelOverrides": cleaned,
        "overrideCount": len(cleaned),
    }


def _preview_wheel_layout(game_root: Path, car_content_path: str) -> dict[str, Any]:
    car_data = resolve_content_path(game_root, f"{car_content_path}\\data\\cardata.car")
    if not car_data.exists():
        car_dir = resolve_content_path(game_root, car_content_path)
        data_dir = car_dir / "data"
        candidates = sorted(data_dir.glob("*cardata.car"), key=lambda row: row.name.lower()) if data_dir.exists() else []
        if candidates:
            car_data = candidates[0]
    result: dict[str, Any] = {
        "source": str(car_data),
        "wheelbase": None,
        "frontTrack": None,
        "rearTrack": None,
    }
    if not car_data.exists():
        return result
    data = car_data.read_bytes()
    result["wheelbase"] = _proto_float_at(data, "2.2")
    result["frontTrack"] = _proto_float_at(data, "2.52")
    result["rearTrack"] = _proto_float_at(data, "2.53")
    return result


def _derive_wheel_mesh_ref(game_root: Path, source_rel: str, from_suffix: str, to_suffix: str) -> tuple[str, Path] | None:
    rel = str(source_rel or "")
    pattern = re.compile(rf"({re.escape(from_suffix)})(\.mesh)$", re.I)
    if not pattern.search(rel):
        return None
    next_rel = pattern.sub(f"{to_suffix}\\2", rel)
    path = resolve_content_path(game_root, next_rel)
    if not path.exists():
        return None
    return next_rel, path


def _preview_tyre_texture_set_ref(game_root: Path, compatiblerim_path: Path | None, compatibletyres_refs: list[str]) -> str | None:
    compat_text = str(compatiblerim_path or "").lower()
    tyre_refs_text = "\n".join(str(ref or "").lower() for ref in compatibletyres_refs)
    tyre_file_text = ""
    for ref in compatibletyres_refs:
        path = resolve_content_path(game_root, ref)
        if not path.exists():
            continue
        try:
            tyre_file_text += "\n" + "\n".join(_proto_utf8_strings(path.read_bytes())).lower()
        except Exception:
            continue
    haystack = "\n".join([compat_text, tyre_refs_text, tyre_file_text])
    direct_refs = re.findall(r"content\\cars\\[^\"'\s]+?\.tyretextures", haystack, flags=re.I)
    for ref in direct_refs:
        resolved = resolve_content_path(game_root, ref)
        if resolved.exists():
            return norm_slashes(ref)
    is_wet = "racing_wet" in haystack or re.search(r"\bwet\b", haystack, re.I)
    is_slick = "racing_slicks" in haystack or re.search(r"\bslick\b", haystack, re.I)
    if "michelin" in haystack and (is_slick or "cup" in haystack):
        return "content\\cars\\common_assets\\parts\\tyres\\textures\\racing_cup_1\\tyre_racing_cup_1.tyretextures"
    if "michelin" in haystack and is_wet:
        return "content\\cars\\common_assets\\parts\\tyres\\textures\\racing_cup_w_1\\tyre_racing_cup_w_1.tyretextures"
    if "pirelli" in haystack and ("racing_slicks" in haystack or "slick" in haystack or "gt3" in haystack or "sro" in haystack):
        return "content\\cars\\common_assets\\parts\\tyres\\textures\\racing_dhf_1\\tyre_racing_dhf_1.tyretextures"
    if "pirelli" in haystack and ("racing_wet" in haystack or "wet" in haystack):
        return "content\\cars\\common_assets\\parts\\tyres\\textures\\racing_whb_1\\tyre_racing_whb_1.tyretextures"
    return None


def _is_tyretextures_ref(value: str | None) -> bool:
    text = norm_slashes(str(value or "")).strip().lower()
    return text.startswith("content\\") and text.endswith(".tyretextures")


def _tyre_texture_family_from_text(value: str | None) -> str:
    text = norm_slashes(str(value or "")).strip().lower()
    if "\\tyres\\vintage\\" in text or "\\textures\\vintage_" in text:
        return "vintage"
    if "\\tyres\\racing_" in text or "\\meshes\\racing\\" in text or "\\textures\\racing_" in text:
        return "racing"
    if "\\tyres\\supercar\\" in text or "\\textures\\supercar_" in text:
        return "supercar"
    if "\\tyres\\hypercar\\" in text or "\\textures\\hypercar_" in text:
        return "hypercar"
    if "\\tyres\\road\\" in text or "\\meshes\\road\\" in text or "\\textures\\road_" in text:
        return "road"
    if "\\textures\\eco_" in text:
        return "eco"
    if "\\textures\\special_" in text:
        return "special"
    return ""


def _tyre_texture_family_from_folder(folder: str) -> str:
    key = str(folder or "").strip().lower()
    for family in ("racing", "vintage", "road", "supercar", "hypercar", "eco", "special"):
        if key == family or key.startswith(f"{family}_"):
            return family
    return ""


def _tyre_texture_metadata(path: Path, game_root: Path) -> dict[str, Any] | None:
    if not path.exists() or path.suffix.lower() != ".tyretextures":
        return None
    folder = path.parent.name
    rel = game_rel_from_disk(game_root, path) or norm_slashes(str(path))
    name = ""
    brand = ""
    try:
        for field in parse_fields(path.read_bytes()):
            if field.wire != 2:
                continue
            text = is_printable_utf8(field.value)
            if text is None:
                continue
            if field.number == 2:
                name = text
            elif field.number == 102:
                brand = text
    except Exception:
        return None
    family = _tyre_texture_family_from_folder(folder) or _tyre_texture_family_from_text(rel)
    label = brand or name or folder
    if name and brand and brand.lower() not in name.lower():
        label = f"{brand} - {name}"
    return {
        "value": folder,
        "label": label,
        "brand": brand,
        "name": name,
        "family": family,
        "refs": [norm_slashes(rel)],
        "fallback": "",
    }


def _custom_tyre_brand_manifest_paths() -> list[Path]:
    root = get_tyre_brands_root()
    if not root.exists():
        return []
    return sorted(root.glob("*/*/manifest.json"), key=lambda path: str(path).lower())


def _load_custom_tyre_brand_manifest(path: Path) -> dict[str, Any] | None:
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return None
    if not isinstance(data, dict) or data.get("type") != "liverylab_custom_tyre_brand":
        return None
    data["_manifestPath"] = str(path)
    data["_assetRoot"] = str(path.parent)
    return data


def _custom_tyre_brand_manifest_by_id(brand_id: str) -> dict[str, Any] | None:
    key = str(brand_id or "").strip().lower()
    if not key:
        return None
    for manifest_path in _custom_tyre_brand_manifest_paths():
        manifest = _load_custom_tyre_brand_manifest(manifest_path)
        if not manifest:
            continue
        if str(manifest.get("id") or manifest_path.parent.name).strip().lower() == key:
            return manifest
    return None


def _custom_tyre_brand_source_path(manifest: dict[str, Any], key: str, fallback_name: str) -> Path | None:
    asset_root = Path(str(manifest.get("_assetRoot") or ""))
    value = str(manifest.get(key) or "").strip()
    candidates: list[Path] = []
    if value:
        raw = Path(value)
        if raw.is_absolute():
            candidates.append(raw)
        candidates.append(asset_root / Path(value).name)
        candidates.append(asset_root / value)
    candidates.append(asset_root / fallback_name)
    for candidate in candidates:
        if candidate.exists() and candidate.suffix.lower() == ".png":
            return candidate
    return None


def _custom_tyre_brand_display_source_path(manifest: dict[str, Any], original_key: str, key: str, fallback_name: str) -> Path | None:
    original = str(manifest.get(original_key) or "").strip()
    if original:
        candidate = Path(original).expanduser()
        if candidate.exists() and candidate.suffix.lower() == ".png":
            return candidate
    return _custom_tyre_brand_source_path(manifest, key, fallback_name)


def _custom_tyre_brand_slick_source_path(manifest: dict[str, Any]) -> Path | None:
    return _custom_tyre_brand_source_path(manifest, "sourcePng", "source_slick.png")


def _custom_tyre_brand_blur_source_path(manifest: dict[str, Any]) -> Path | None:
    return _custom_tyre_brand_source_path(manifest, "sourceBlurPng", "source_blur.png")


def _custom_tyre_brand_mark_source_path(manifest: dict[str, Any]) -> Path | None:
    return _custom_tyre_brand_source_path(manifest, "markPng", "mark.png")


def _custom_tyre_brand_slick_display_source_path(manifest: dict[str, Any]) -> Path | None:
    return _custom_tyre_brand_display_source_path(manifest, "sourcePngOriginalPath", "sourcePng", "source_slick.png")


def _custom_tyre_brand_blur_display_source_path(manifest: dict[str, Any]) -> Path | None:
    return _custom_tyre_brand_display_source_path(manifest, "sourceBlurPngOriginalPath", "sourceBlurPng", "source_blur.png")


def _custom_tyre_brand_mark_display_source_path(manifest: dict[str, Any]) -> Path | None:
    return _custom_tyre_brand_display_source_path(manifest, "markPngOriginalPath", "markPng", "mark.png")


def _validate_square_png_source(source_png: str | Path, label: str) -> Path:
    source = Path(source_png).expanduser()
    if source.suffix.lower() != ".png":
        raise ValueError(f"{label} must be a PNG file")
    if not source.exists():
        raise FileNotFoundError(f"{label} not found: {source}")
    from PIL import Image
    with Image.open(source) as image:
        width, height = image.size
    if width != height:
        raise ValueError(f"{label} must be square. Selected image is {width}x{height}.")
    return source.resolve()


def _copy_optional_tyre_brand_mark_png(mark_png: str | Path | None, asset_root: Path, remove_when_empty: bool = False) -> str:
    target = asset_root / "mark.png"
    source_text = str(mark_png or "").strip()
    if not source_text:
        if remove_when_empty:
            target.unlink(missing_ok=True)
        return ""
    source = _validate_square_png_source(source_text, "Brand mark PNG")
    target.parent.mkdir(parents=True, exist_ok=True)
    if source != target.resolve():
        shutil.copy2(source, target)
    return target.name


def _custom_tyre_brand_options(family: str = "") -> list[dict[str, Any]]:
    selected_family = str(family or "").strip().lower()
    options: list[dict[str, Any]] = []
    for manifest_path in _custom_tyre_brand_manifest_paths():
        manifest = _load_custom_tyre_brand_manifest(manifest_path)
        if not manifest:
            continue
        option_family = str(manifest.get("family") or "").strip().lower()
        if selected_family and option_family != selected_family:
            continue
        refs = [norm_slashes(str(ref)) for ref in (manifest.get("refs") or []) if str(ref or "").strip()]
        fallback = norm_slashes(str(manifest.get("fallback") or "")).strip()
        if not refs:
            tyre_ref = norm_slashes(str(manifest.get("tyretexturesRel") or "")).strip()
            if tyre_ref:
                refs = [tyre_ref]
        if option_family == "racing":
            wet_ref = norm_slashes(RACING_GENERIC_WET_TYRETEXTURES_REF)
            lower_refs = [ref.lower() for ref in refs]
            if wet_ref.lower() not in lower_refs and not any("_w_" in ref or "generic_w" in ref for ref in lower_refs):
                refs.append(wet_ref)
            if not fallback:
                fallback = norm_slashes(RACING_GENERIC_SLICK_TYRETEXTURES_REF)
        value = str(manifest.get("id") or manifest_path.parent.name).strip().lower()
        options.append(
            {
                "value": value,
                "label": str(manifest.get("label") or manifest.get("brandName") or value),
                "brand": str(manifest.get("brandName") or manifest.get("label") or value),
                "name": str(manifest.get("displayName") or manifest.get("label") or value),
                "family": option_family,
                "refs": refs,
                "fallback": fallback,
                "custom": True,
                "manifestPath": str(manifest_path),
            }
        )
    return options


def _static_tyre_texture_options(family: str = "") -> list[dict[str, Any]]:
    selected_family = str(family or "").strip().lower()
    options: list[dict[str, Any]] = []
    for key, option in TYRE_BRAND_TEXTURE_OVERRIDES.items():
        option_family = str(option.get("family") or "").strip().lower()
        if selected_family and option_family != selected_family:
            continue
        options.append(
            {
                "value": key,
                "label": str(option.get("label") or key),
                "brand": str(option.get("brand") or key),
                "family": option_family,
                "refs": [norm_slashes(str(ref)) for ref in (option.get("refs") or [])],
                "fallback": norm_slashes(str(option.get("fallback") or "")),
                "aliases": list(option.get("aliases") or []),
            }
        )
    return options


def _available_tyre_texture_options(game_root: Path, family: str) -> list[dict[str, Any]]:
    selected_family = str(family or "").strip().lower()
    if not selected_family:
        return []
    if selected_family == "racing":
        return _static_tyre_texture_options("racing") + _custom_tyre_brand_options("racing")
    textures_root = game_root / "content" / "cars" / "common_assets" / "parts" / "tyres" / "textures"
    if not textures_root.exists():
        return []
    options: list[dict[str, Any]] = []
    for path in sorted(textures_root.glob("*/*.tyretextures")):
        meta = _tyre_texture_metadata(path, game_root)
        if not meta or str(meta.get("family") or "").lower() != selected_family:
            continue
        options.append(meta)
    return options + _custom_tyre_brand_options(selected_family)


def _resolve_tyre_texture_option(value: str, options: list[dict[str, Any]] | None = None) -> dict[str, Any] | None:
    key = str(value or "").strip().lower()
    if not key:
        return None
    all_options = list(options or []) or _static_tyre_texture_options()
    for option in all_options:
        option_key = str(option.get("value") or "").strip().lower()
        aliases = [str(alias or "").strip().lower() for alias in option.get("aliases") or []]
        brand = str(option.get("brand") or "").strip().lower()
        if key == option_key or key in aliases or (brand and key == brand and len([row for row in all_options if str(row.get("brand") or "").strip().lower() == brand]) == 1):
            return option
    legacy = TYRE_BRAND_TEXTURE_OVERRIDES.get(key)
    if legacy:
        return _static_tyre_texture_options(str(legacy.get("family") or ""))[0]
    return None


def _tyre_texture_option_refs(option: dict[str, Any]) -> list[str]:
    refs = [norm_slashes(str(ref)) for ref in (option.get("refs") or []) if str(ref or "").strip()]
    fallback = norm_slashes(str(option.get("fallback") or "")).strip()
    if fallback:
        refs.append(fallback)
    return refs


def _selection_from_tyretexture_refs(refs: list[str], options: list[dict[str, Any]]) -> str:
    normalized = [norm_slashes(str(ref or "")).strip().lower() for ref in refs if str(ref or "").strip()]
    if not normalized:
        return "default"
    normalized_set = set(normalized)
    for option in options:
        option_refs = [ref.lower() for ref in _tyre_texture_option_refs(option)]
        if option_refs and set(option_refs) == normalized_set:
            return str(option.get("value") or "")
    return "custom"


def _visualpreset_tyretexture_refs(data: bytes) -> list[dict[str, Any]]:
    refs: list[dict[str, Any]] = []
    for field in parse_fields(data):
        if field.wire != 2:
            continue
        text = is_printable_utf8(field.value)
        if not _is_tyretextures_ref(text):
            continue
        refs.append({"field": field.number, "path": norm_slashes(str(text))})
    return refs


def _brand_from_tyretexture_refs(refs: list[str]) -> str:
    haystack = "\n".join(norm_slashes(str(ref or "")).lower() for ref in refs)
    if "dunlop" in haystack:
        return "dunlop"
    if "pirelli" in haystack or "racing_dhf_1" in haystack or "racing_whb_1" in haystack or "road_2" in haystack or "supercar_2" in haystack:
        return "pirelli"
    if "michelin" in haystack or "racing_cup_1" in haystack or "racing_cup_w_1" in haystack or "racing_renn_1" in haystack or "vintage_1" in haystack or "road_1" in haystack:
        return "michelin"
    if "generic" in haystack:
        return "generic"
    return ""


def _tyre_brand_label(brand: str) -> str:
    key = str(brand or "").strip().lower()
    override = _resolve_tyre_texture_option(key)
    if override:
        return str(override.get("label") or key.title())
    if key == "generic":
        return "Generic"
    if key == "default":
        return "Default"
    return key.title() if key else ""


def _visualpreset_tyre_brand_info(visual_path: Path | None) -> dict[str, Any]:
    if not visual_path or not visual_path.exists():
        return {"selection": "default", "brand": "", "label": "Default", "source": "missing", "refs": []}
    refs = _visualpreset_tyretexture_refs(visual_path.read_bytes())
    paths = [str(row.get("path") or "") for row in refs]
    brand = _brand_from_tyretexture_refs(paths)
    if not paths:
        return {"selection": "default", "brand": "", "label": "Default", "source": "default", "refs": []}
    return {
        "selection": brand if brand in TYRE_BRAND_TEXTURE_OVERRIDES else "custom",
        "brand": brand,
        "label": _tyre_brand_label(brand) if brand else "Custom",
        "source": "visual_preset",
        "refs": paths,
        "fields": refs,
    }


def _infer_tyre_context_from_cardesign(entry: dict[str, Any], game_root: Path) -> dict[str, Any]:
    files = dict(entry.get("files") or {})
    cardesign_rel = str(files.get("cardesign") or "").strip()
    if not cardesign_rel:
        return {"brand": "", "label": "", "source": "", "refs": [], "family": "", "availableOptions": [], "supported": False}
    cardesign_path = _skin_file_disk_path(game_root, entry, cardesign_rel)
    if not cardesign_path or not cardesign_path.exists():
        return {"brand": "", "label": "", "source": "", "refs": [], "family": "", "availableOptions": [], "supported": False}
    try:
        details = extract_cardesign_details(cardesign_path.read_bytes(), root=game_root)
        rim_ref = str(details.get("rim_path") or "").strip()
        rim_path = resolve_content_path(game_root, rim_ref)
        compatibletyres_refs: list[str] = []
        compat_strings: list[str] = []
        rim_strings: list[str] = []
        if rim_path.exists() and rim_path.suffix.lower() == ".compatiblerim":
            rim_data = rim_path.read_bytes()
            compatibletyres_refs = _compatiblerim_compatibletyres_refs(rim_data)
            rim_strings = _proto_utf8_strings(rim_data)
        for ref in compatibletyres_refs:
            path = resolve_content_path(game_root, ref)
            if not path.exists():
                continue
            compat_strings.extend(_proto_utf8_strings(path.read_bytes()))
        tyre_ref = _preview_tyre_texture_set_ref(game_root, rim_path if rim_path.exists() else None, compatibletyres_refs)
    except Exception:
        return {"brand": "", "label": "", "source": "", "refs": [], "family": "", "availableOptions": [], "supported": False}
    family = ""
    for value in [*compat_strings, *compatibletyres_refs, *rim_strings, tyre_ref or "", rim_ref]:
        family = _tyre_texture_family_from_text(value)
        if family:
            break
    options = _available_tyre_texture_options(game_root, family)
    brand = _brand_from_tyretexture_refs([tyre_ref or "", rim_ref, *compatibletyres_refs])
    support_message = ""
    if not family:
        support_message = "Tyre brand selection is not supported for this tyre setup."
    elif len(options) <= 1:
        support_message = "Tyre brand selection is not supported for this tyre type."
    return {
        "brand": brand,
        "label": _tyre_brand_label(brand) if brand else "",
        "source": "cardesign",
        "refs": [value for value in [tyre_ref, rim_ref, *compatibletyres_refs] if value],
        "family": family,
        "availableOptions": options,
        "supported": len(options) > 1,
        "supportMessage": support_message,
    }


def _infer_default_tyre_brand_from_cardesign(entry: dict[str, Any], game_root: Path) -> dict[str, Any]:
    return _infer_tyre_context_from_cardesign(entry, game_root)


def describe_my_skin_tyre_brand(entry: dict[str, Any], game_root: Path) -> dict[str, Any]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    visual_path = _skin_file_disk_path(game_root, entry, str(files.get("visual_preset") or ""))
    visual_info = _visualpreset_tyre_brand_info(visual_path)
    default_info = _infer_tyre_context_from_cardesign(entry, game_root)
    options = list(default_info.get("availableOptions") or [])
    visual_refs = [str(ref or "") for ref in visual_info.get("refs") or []]
    selection = _selection_from_tyretexture_refs(visual_refs, options) if visual_refs else "default"
    selected_option = _resolve_tyre_texture_option(selection, options) if selection not in {"default", "custom"} else None
    if selected_option:
        visual_info["selection"] = selection
        visual_info["brand"] = str(selected_option.get("brand") or visual_info.get("brand") or "")
        visual_info["label"] = str(selected_option.get("label") or visual_info.get("label") or "")
    elif visual_refs and selection == "custom":
        visual_info["selection"] = "custom"
    if visual_info.get("source") == "visual_preset":
        visual_info["defaultBrand"] = default_info.get("brand") or ""
        visual_info["defaultLabel"] = default_info.get("label") or ""
        visual_info["family"] = default_info.get("family") or ""
        visual_info["availableOptions"] = options
        visual_info["supported"] = bool(default_info.get("supported"))
        visual_info["supportMessage"] = default_info.get("supportMessage") or ""
        return visual_info
    return {
        **visual_info,
        "defaultBrand": default_info.get("brand") or "",
        "defaultLabel": default_info.get("label") or "",
        "defaultSource": default_info.get("source") or "",
        "defaultRefs": default_info.get("refs") or [],
        "family": default_info.get("family") or "",
        "availableOptions": options,
        "supported": bool(default_info.get("supported")),
        "supportMessage": default_info.get("supportMessage") or "",
    }


def edit_visualpreset_tyre_brand_data(data: bytes, brand: str, available_options: list[dict[str, Any]] | None = None) -> bytes:
    key = str(brand or "default").strip().lower()
    if key in {"", "default", "from_car", "car"}:
        override = None
    else:
        override = _resolve_tyre_texture_option(key, available_options)
        if override is None:
            raise ValueError(f"Unsupported tyre option: {brand}")
    new_fields: list[ProtoField] = []
    changed = False
    for field in parse_fields(data):
        text = is_printable_utf8(field.value) if field.wire == 2 else None
        if field.number in {7, 17} and _is_tyretextures_ref(text):
            changed = True
            continue
        new_fields.append(field)
    if override is not None:
        for ref in override.get("refs") or []:
            new_fields.append(ProtoField(17, 2, norm_slashes(str(ref)).encode("utf-8")))
        fallback = str(override.get("fallback") or "").strip()
        if fallback:
            new_fields.append(ProtoField(7, 2, norm_slashes(fallback).encode("utf-8")))
    new_data = build_fields(new_fields)
    return new_data if changed or new_data != data else data


def _canonical_tyre_brand_selection(brand: Any, available_options: list[dict[str, Any]] | None = None) -> str:
    key = str(brand or "default").strip().lower()
    if key in {"", "default", "from_car", "car"}:
        return "default"
    option = _resolve_tyre_texture_option(key, available_options)
    if option is None:
        raise ValueError(f"Unsupported tyre option: {brand}")
    return str(option.get("value") or key).strip().lower()


def _tyre_brand_selection_from_visual_data(data: bytes, available_options: list[dict[str, Any]] | None = None) -> str:
    refs = [str(row.get("path") or "") for row in _visualpreset_tyretexture_refs(data)]
    return _selection_from_tyretexture_refs(refs, list(available_options or [])) if refs else "default"


def _persisted_tyre_brand_selection(
    info: dict[str, Any],
    selection: str,
    available_options: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
    canonical = _canonical_tyre_brand_selection(selection, available_options)
    result = dict(info)
    result["selection"] = canonical
    result["requestedSelection"] = canonical
    result["selectionExplicit"] = True
    if canonical != "default":
        option = _resolve_tyre_texture_option(canonical, available_options)
        if option:
            result["brand"] = str(option.get("brand") or result.get("brand") or "")
            result["label"] = str(option.get("label") or option.get("name") or result.get("label") or canonical)
    return result


def _presented_tyre_brand_selection(info: dict[str, Any], saved: dict[str, Any] | None) -> dict[str, Any]:
    stored = saved if isinstance(saved, dict) else {}
    if stored.get("selectionExplicit") is not True:
        return info
    requested = str(stored.get("requestedSelection") or stored.get("selection") or "default").strip().lower()
    options = list(info.get("availableOptions") or [])
    actual = str(info.get("selection") or "default").strip().lower()
    try:
        result = _persisted_tyre_brand_selection(info, requested, options)
        canonical = str(result.get("selection") or "default")
    except ValueError:
        result = {
            **info,
            "selection": requested,
            "requestedSelection": requested,
            "selectionExplicit": True,
        }
        canonical = requested
    result["localVisualSelection"] = actual
    result["localVisualInSync"] = actual == canonical
    return result


def _ensure_persisted_tyre_brand_selection(entry: dict[str, Any], game_root: Path) -> dict[str, Any]:
    saved = entry.get("tyre_brand") if isinstance(entry.get("tyre_brand"), dict) else {}
    if saved.get("selectionExplicit") is not True:
        return {"enabled": False, "changed": False, "selection": "", "actions": []}
    requested = str(saved.get("requestedSelection") or saved.get("selection") or "default").strip().lower()
    current_info = describe_my_skin_tyre_brand(entry, game_root)
    options = list(current_info.get("availableOptions") or [])
    canonical = _canonical_tyre_brand_selection(requested, options)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    visual_rel = str(files.get("visual_preset") or "").strip()
    visual_path = _skin_file_disk_path(game_root, entry, visual_rel)
    if visual_path is None or not visual_path.exists():
        raise FileNotFoundError("Local visual preset for the saved tyre brand selection was not found")
    local_visual = _ensure_local_skin_file_copy(game_root, entry, visual_rel, source=visual_path)
    if local_visual is None or not local_visual.exists():
        raise FileNotFoundError("Local visual preset for the saved tyre brand selection could not be prepared")
    old_data = local_visual.read_bytes()
    actual = _tyre_brand_selection_from_visual_data(old_data, options)
    changed = actual != canonical
    writer_data: dict[str, Any] = {}
    if changed:
        new_data = edit_visualpreset_tyre_brand_data(old_data, canonical, options)
        verified = _tyre_brand_selection_from_visual_data(new_data, options)
        if verified != canonical:
            raise RuntimeError(
                "Saved tyre brand selection could not be restored in the local visual preset: "
                f"expected {canonical}, found {verified}"
            )
        writer = GameFileWriter(_entry_local_root(entry) or local_visual.parent, "restore saved My Skin tyre brand", [local_visual])
        _write_game_bytes(local_visual, new_data, writer, label="restore visual preset tyre brand")
        writer_data = writer.to_dict()
    refreshed = describe_my_skin_tyre_brand(entry, game_root)
    entry["tyre_brand"] = _json_ready(_persisted_tyre_brand_selection(refreshed, canonical, options))
    return {
        "enabled": True,
        "changed": changed,
        "selection": canonical,
        "actions": [
            f"saved tyre brand restored in local visual preset: {canonical}"
            if changed else
            f"saved tyre brand verified: {canonical}"
        ],
        "writer": writer_data,
    }


def _tyretextures_text_fields(path: Path) -> dict[int, str]:
    refs: dict[int, str] = {}
    for field in parse_fields(path.read_bytes()):
        if field.wire != 2:
            continue
        text = is_printable_utf8(field.value)
        if text is not None:
            refs[field.number] = text
    return refs


def _patch_tyretextures_data(data: bytes, replacements: dict[int, str]) -> bytes:
    fields: list[ProtoField] = []
    for field in parse_fields(data):
        if field.wire == 2 and field.number in replacements:
            fields.append(ProtoField(field.number, field.wire, norm_slashes(replacements[field.number]).encode("utf-8")))
        else:
            fields.append(field)
    return build_fields(fields)


def _write_png_as_texturemips_for_template(source_png: Path, target_texture: Path, template_texture: Path) -> dict[str, Any]:
    if not source_png.exists():
        raise FileNotFoundError(f"Source PNG not found: {source_png}")
    template_mips = template_texture.with_suffix(".texturemips")
    if not template_texture.exists() or not template_mips.exists():
        raise FileNotFoundError(f"Template texture or texturemips missing: {template_texture}")
    shutil.copy2(template_texture, target_texture)
    desc = parse_texture_descriptor(target_texture)
    format_name, auto_note = choose_auto_format(template_texture, desc, template_mips.stat().st_size)
    target_texture.parent.mkdir(parents=True, exist_ok=True)
    with tempfile.TemporaryDirectory(prefix="liverylab_tyre_brand_") as tmp:
        dds_path = Path(tmp) / f"{target_texture.stem}.dds"
        convert_image_to_dds_with_texconv(
            source_png,
            dds_path,
            format_name,
            None,
            int(desc["mips"]),
            (int(desc["width"]), int(desc["height"])),
        )
        mips_data = texturemips_bytes_from_dds(target_texture, dds_path)
    mips_path = target_texture.with_suffix(".texturemips")
    safe_write_bytes(mips_path, mips_data)
    return {
        "texture": str(target_texture),
        "texturemips": str(mips_path),
        "format": format_name,
        "note": auto_note,
        "width": int(desc["width"]),
        "height": int(desc["height"]),
        "mips": int(desc["mips"]),
        "bytes": len(mips_data),
    }


def _racing_tyre_template_path(game_root: Path, template_id: str) -> Path:
    clean = sanitize_slug(template_id or "racing_cup_1")
    if not clean.startswith("racing_"):
        clean = f"racing_{clean}"
    candidate = game_root / "content" / "cars" / "common_assets" / "parts" / "tyres" / "textures" / clean / f"tyre_{clean}.tyretextures"
    if candidate.exists():
        return candidate
    fallback = game_root / "content" / "cars" / "common_assets" / "parts" / "tyres" / "textures" / "racing_cup_1" / "tyre_racing_cup_1.tyretextures"
    if fallback.exists():
        return fallback
    raise FileNotFoundError("Racing tyre template was not found")


def list_custom_tyre_brands(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    items = []
    for manifest_path in _custom_tyre_brand_manifest_paths():
        manifest = _load_custom_tyre_brand_manifest(manifest_path)
        if not manifest:
            continue
        refs = [norm_slashes(str(ref)) for ref in (manifest.get("refs") or []) if str(ref or "").strip()]
        slick_source = _custom_tyre_brand_slick_display_source_path(manifest)
        blur_source = _custom_tyre_brand_blur_display_source_path(manifest)
        mark_source = _custom_tyre_brand_mark_display_source_path(manifest)
        files = []
        for rel in manifest.get("files") or []:
            rel_norm = normalize_game_rel_path(str(rel or ""))
            local = Path(str(manifest.get("_assetRoot") or manifest_path.parent)) / Path(*split_content_path(rel_norm))
            game = game_root / Path(*split_content_path(rel_norm))
            files.append({
                "rel": rel_norm,
                "local": str(local),
                "localExists": local.exists(),
                "game": str(game),
                "gameExists": game.exists(),
            })
        items.append({
            "id": str(manifest.get("id") or manifest_path.parent.name),
            "label": str(manifest.get("label") or manifest.get("brandName") or manifest_path.parent.name),
            "brandName": str(manifest.get("brandName") or ""),
            "family": str(manifest.get("family") or ""),
            "template": str(manifest.get("template") or ""),
            "refs": refs,
            "fallback": str(manifest.get("fallback") or ""),
            "sourcePng": str(manifest.get("sourcePng") or ""),
            "sourceBlurPng": str(manifest.get("sourceBlurPng") or ""),
            "markPng": str(manifest.get("markPng") or ""),
            "sourcePngOriginalPath": str(manifest.get("sourcePngOriginalPath") or ""),
            "sourceBlurPngOriginalPath": str(manifest.get("sourceBlurPngOriginalPath") or ""),
            "markPngOriginalPath": str(manifest.get("markPngOriginalPath") or ""),
            "sourcePngPath": str(slick_source) if slick_source else "",
            "sourceBlurPngPath": str(blur_source) if blur_source else "",
            "markPngPath": str(mark_source) if mark_source else "",
            "manifestPath": str(manifest_path),
            "assetRoot": str(manifest.get("_assetRoot") or manifest_path.parent),
            "files": files,
            "createdAt": str(manifest.get("createdAt") or ""),
            "updatedAt": str(manifest.get("updatedAt") or ""),
        })
    return {"root": str(get_tyre_brands_root()), "items": _json_ready(items)}


def _tyre_brand_slug_from_id(value: Any, fallback: str = "custom") -> str:
    clean = sanitize_slug(str(value or fallback))
    clean = re.sub(r"^(?:tyre_)?racing_", "", clean, flags=re.I)
    clean = re.sub(r"_?1$", "", clean, flags=re.I)
    return clean or sanitize_slug(fallback) or "custom"


def _tyre_brand_id_from_slug(slug: str) -> str:
    return f"racing_{_tyre_brand_slug_from_id(slug)}_1"


def _tyre_brand_unique_slug(parent: Path, preferred: str) -> str:
    base = _tyre_brand_slug_from_id(preferred)
    candidate = base
    index = 2
    while (parent / candidate).exists():
        candidate = f"{base}_import" if index == 2 else f"{base}_import_{index}"
        index += 1
    return candidate


def _tyre_brand_package_member_path(package_root: Path, rel_path: Any, label: str) -> Path:
    raw_text = str(rel_path or "").strip()
    raw_unix = raw_text.replace("\\", "/")
    if re.match(r"^[A-Za-z]:", raw_unix) or raw_unix.startswith("/"):
        raise ValueError(f"Tyre brand package {label} path is unsafe: {raw_text}")
    rel_text = normalize_game_rel_path(raw_text)
    if not rel_text:
        raise ValueError(f"Tyre brand package {label} path is missing")
    rel_disk = Path(*split_content_path(rel_text))
    if rel_disk.is_absolute() or ".." in rel_disk.parts:
        raise ValueError(f"Tyre brand package {label} path is unsafe: {rel_text}")
    path = (package_root / rel_disk).resolve()
    try:
        path.relative_to(package_root.resolve())
    except ValueError as exc:
        raise ValueError(f"Tyre brand package {label} path escapes the package: {rel_text}") from exc
    return path


def _clean_tyre_brand_package_manifest(manifest: dict[str, Any], new_id: str | None = None) -> dict[str, Any]:
    data = manifest if isinstance(manifest, dict) else {}
    brand_id = _tyre_brand_id_from_slug(new_id or data.get("id") or data.get("label") or "custom")
    clean = {
        "formatVersion": int(data.get("formatVersion") or 1),
        "type": "liverylab_custom_tyre_brand",
        "family": "racing",
        "id": brand_id,
        "label": str(data.get("label") or data.get("brandName") or brand_id).strip() or brand_id,
        "brandName": str(data.get("brandName") or data.get("label") or brand_id).strip() or brand_id,
        "displayName": str(data.get("displayName") or data.get("label") or brand_id).strip() or brand_id,
        "template": normalize_game_rel_path(str(data.get("template") or "")),
        "tyretexturesRel": normalize_game_rel_path(str(data.get("tyretexturesRel") or "")),
        "refs": [normalize_game_rel_path(str(ref)) for ref in (data.get("refs") or []) if str(ref or "").strip()],
        "fallback": normalize_game_rel_path(str(data.get("fallback") or RACING_GENERIC_SLICK_TYRETEXTURES_REF)),
        "files": [normalize_game_rel_path(str(rel)) for rel in (data.get("files") or []) if str(rel or "").strip()],
        "sourcePng": Path(str(data.get("sourcePng") or "source.png").replace("\\", "/")).name,
        "sourceBlurPng": Path(str(data.get("sourceBlurPng") or "source_blur.png").replace("\\", "/")).name,
        "createdAt": str(data.get("createdAt") or datetime.now().isoformat(timespec="seconds")),
        "updatedAt": datetime.now().isoformat(timespec="seconds"),
    }
    mark_png = Path(str(data.get("markPng") or "").replace("\\", "/")).name.strip()
    if mark_png:
        clean["markPng"] = mark_png
    for key in ("sourcePngOriginalPath", "sourceBlurPngOriginalPath", "markPngOriginalPath"):
        value = str(data.get(key) or "").strip()
        if value:
            clean[key] = value
    if clean["tyretexturesRel"] and clean["tyretexturesRel"] not in clean["refs"]:
        clean["refs"].insert(0, clean["tyretexturesRel"])
    wet_ref = normalize_game_rel_path(RACING_GENERIC_WET_TYRETEXTURES_REF)
    if wet_ref not in clean["refs"]:
        clean["refs"].append(wet_ref)
    return clean


def _rebase_tyre_brand_ref(value: Any, old_id: str, new_id: str) -> str:
    text = normalize_game_rel_path(str(value or ""))
    if not text or old_id == new_id:
        return text
    return re.sub(re.escape(old_id), new_id, text, flags=re.I)


def _patch_tyre_brand_refs_in_data(data: bytes, old_id: str, new_id: str) -> bytes:
    if old_id == new_id:
        return data
    fields: list[ProtoField] = []
    changed = False
    for field in parse_fields(data):
        text = is_printable_utf8(field.value) if field.wire == 2 else None
        if text is not None and old_id.lower() in text.lower():
            next_text = re.sub(re.escape(old_id), new_id, norm_slashes(text), flags=re.I)
            fields.append(ProtoField(field.number, field.wire, next_text.encode("utf-8")))
            changed = True
        else:
            fields.append(field)
    return build_fields(fields) if changed else data


def export_custom_tyre_brand_package(brand_id: str, out_dir: str | Path | None = None, root: str | Path | None = None, zip_output: bool = True) -> dict[str, Any]:
    manifest = _custom_tyre_brand_manifest_by_id(brand_id)
    if not manifest:
        raise FileNotFoundError(f"Custom tyre brand not found: {brand_id}")
    family = str(manifest.get("family") or "").strip().lower()
    if family != "racing":
        raise ValueError("Only racing tyre brand packages are supported")
    clean_id = str(manifest.get("id") or brand_id).strip().lower()
    clean_slug = _tyre_brand_slug_from_id(clean_id)
    package_name = short_export_package_name(
        "tyre",
        manifest.get("label") or manifest.get("brandName") or clean_slug,
        clean_id,
    )
    export_root = _tyre_brand_package_root(out_dir, root)
    package_root = export_root / package_name
    if package_root.exists():
        shutil.rmtree(package_root)
    package_root.mkdir(parents=True, exist_ok=True)

    asset_root = Path(str(manifest.get("_assetRoot") or ""))
    brand_dir = package_root / "brand"
    brand_dir.mkdir(parents=True, exist_ok=True)
    brand_manifest = _clean_tyre_brand_package_manifest(manifest, clean_id)
    for key in ("sourcePngOriginalPath", "sourceBlurPngOriginalPath", "markPngOriginalPath"):
        brand_manifest.pop(key, None)
    brand_manifest_path = brand_dir / "manifest.json"
    safe_write_text(brand_manifest_path, json.dumps(_json_ready(brand_manifest), indent=2, ensure_ascii=False))

    copied_files: list[dict[str, Any]] = []
    for rel in brand_manifest.get("files") or []:
        rel_norm = normalize_game_rel_path(str(rel or ""))
        if not rel_norm:
            continue
        source = asset_root / Path(*split_content_path(rel_norm))
        if not source.exists():
            raise FileNotFoundError(f"Custom tyre brand file missing: {source}")
        target = package_root / Path(*split_content_path(rel_norm))
        target.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(source, target)
        copied_files.append({"rel": rel_norm, "path": normalize_game_rel_path(str(target.relative_to(package_root))), "bytes": target.stat().st_size})
    source_files: list[dict[str, Any]] = []
    for key, label_name, default_name in (
        ("sourcePng", "slick", "source_slick.png"),
        ("sourceBlurPng", "blur", "source_blur.png"),
        ("markPng", "mark", "mark.png"),
    ):
        source_png = _custom_tyre_brand_source_path(manifest, key, default_name)
        if not source_png or not source_png.exists():
            continue
        if label_name == "mark":
            source_png = _validate_square_png_source(source_png, "Brand mark PNG")
        target = package_root / "sources" / default_name
        target.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(source_png, target)
        source_files.append({"kind": label_name, "path": normalize_game_rel_path(str(target.relative_to(package_root)))})

    package_manifest = {
        "formatVersion": 1,
        "type": TYRE_BRAND_PACKAGE_TYPE,
        "app": {"name": APP_NAME, "version": APP_VERSION},
        "exportedAt": datetime.now().isoformat(timespec="seconds"),
        "brand": {
            "id": clean_id,
            "label": str(manifest.get("label") or manifest.get("brandName") or clean_id),
            "family": "racing",
            "path": "brand\\manifest.json",
        },
        "files": copied_files,
        "sources": source_files,
    }
    manifest_path = package_root / "manifest.json"
    safe_write_text(manifest_path, json.dumps(_json_ready(package_manifest), indent=2, ensure_ascii=False))
    result = {
        "packageRoot": str(package_root),
        "manifestPath": str(manifest_path),
        "brand": package_manifest["brand"],
        "copiedFiles": copied_files,
        "copiedFileCount": len(copied_files),
        "sourceFiles": source_files,
    }
    if zip_output:
        zip_path = package_zip.export_zip_path(export_root, package_name)
        package_zip.zip_directory(package_root, zip_path)
        shutil.rmtree(package_root, ignore_errors=True)
        result["packagePath"] = str(zip_path)
        result["packageRoot"] = str(zip_path)
        result["manifestPath"] = "manifest.json"
        result["zip"] = True
    return result


def _inspect_custom_tyre_brand_package_root(package_root: str | Path, package_path: str | Path | None = None) -> dict[str, Any]:
    package_root = Path(package_root).expanduser().resolve()
    validate_package_import_structure(
        package_root,
        {"manifest.json", "brand", "content", "sources"},
        "Tyre brand package",
    )
    manifest_path = package_root / "manifest.json"
    if not manifest_path.exists():
        raise FileNotFoundError(f"manifest.json not found in package folder: {package_root}")
    manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    if not isinstance(manifest, dict) or manifest.get("type") != TYRE_BRAND_PACKAGE_TYPE:
        raise ValueError("Tyre brand package manifest is invalid")
    brand_info = manifest.get("brand") if isinstance(manifest.get("brand"), dict) else {}
    brand_path = _tyre_brand_package_member_path(package_root, str(brand_info.get("path") or "brand\\manifest.json"), "brand manifest")
    missing: list[dict[str, Any]] = []
    brand_manifest: dict[str, Any] = {}
    if not brand_path.exists():
        missing.append({"kind": "brand", "path": normalize_game_rel_path(str(brand_path.relative_to(package_root)))})
    else:
        try:
            brand_manifest = json.loads(brand_path.read_text(encoding="utf-8"))
        except Exception:
            brand_manifest = {}
        if not isinstance(brand_manifest, dict) or brand_manifest.get("type") != "liverylab_custom_tyre_brand":
            missing.append({"kind": "brand", "path": normalize_game_rel_path(str(brand_path.relative_to(package_root))), "reason": "invalid manifest"})
    files = manifest.get("files") if isinstance(manifest.get("files"), list) else []
    sources = manifest.get("sources") if isinstance(manifest.get("sources"), list) else []
    for row in files:
        rel = str((row if isinstance(row, dict) else {}).get("rel") or "").strip()
        if not rel:
            missing.append({"kind": "file", "path": "", "reason": "path missing"})
            continue
        path = _tyre_brand_package_member_path(package_root, rel, "file")
        if not path.exists():
            missing.append({"kind": "file", "path": rel})
    for row in sources:
        rel = str((row if isinstance(row, dict) else {}).get("path") or "").strip()
        if not rel:
            continue
        path = _tyre_brand_package_member_path(package_root, rel, "source")
        if not path.exists():
            missing.append({"kind": "source", "path": rel})
    return {
        "packageRoot": str(package_root),
        "manifestPath": str(manifest_path),
        "manifest": manifest,
        "brand": {**brand_info, "manifest": brand_manifest},
        "files": files,
        "sources": sources,
        "fileCount": len(files),
        "missing": missing,
        "missingCount": len(missing),
        "packagePath": str(package_path or package_root),
        "zip": package_zip.is_zip_package(package_path or ""),
    }


def inspect_custom_tyre_brand_package(package_dir: str | Path) -> dict[str, Any]:
    source = Path(package_dir).expanduser()
    with package_zip.package_root(source, get_package_import_temp_root(), allow_folder=True) as package_root:
        return _inspect_custom_tyre_brand_package_root(package_root, source)


def import_custom_tyre_brand_package(package_dir: str | Path, conflict_mode: str = "rename") -> dict[str, Any]:
    source_package = Path(package_dir).expanduser()
    with package_zip.package_root(source_package, get_package_import_temp_root(), allow_folder=True) as package_root:
        return _import_custom_tyre_brand_package_root(package_root, source_package, conflict_mode)


def _import_custom_tyre_brand_package_root(package_root: Path, source_package: Path, conflict_mode: str = "rename") -> dict[str, Any]:
    info = _inspect_custom_tyre_brand_package_root(package_root, source_package)
    if info.get("missingCount"):
        raise FileNotFoundError("Tyre brand package is incomplete")
    package_root = Path(info["packageRoot"])
    brand_info = info.get("brand") if isinstance(info.get("brand"), dict) else {}
    source_manifest = brand_info.get("manifest") if isinstance(brand_info.get("manifest"), dict) else {}
    package_manifest = info.get("manifest") if isinstance(info.get("manifest"), dict) else {}
    if str(source_manifest.get("family") or "").strip().lower() != "racing":
        raise ValueError("Only racing tyre brand packages are supported")

    old_id = str(source_manifest.get("id") or brand_info.get("id") or "racing_custom_1").strip().lower()
    old_slug = _tyre_brand_slug_from_id(old_id)
    target_root = get_tyre_brands_root() / "racing"
    replace_existing = str(conflict_mode or "rename").strip().lower() in {"replace", "overwrite"}
    target_slug = old_slug if replace_existing else _tyre_brand_unique_slug(target_root, old_slug)
    target_id = _tyre_brand_id_from_slug(target_slug)
    target_dir = target_root / target_slug
    moved_existing = ""
    if target_dir.exists():
        if replace_existing:
            moved_existing = _numberplate_package_move_existing(target_dir)
        else:
            target_slug = _tyre_brand_unique_slug(target_root, target_slug)
            target_id = _tyre_brand_id_from_slug(target_slug)
            target_dir = target_root / target_slug
    assert_package_import_target(target_dir, "tyre brand package target directory")
    target_dir.mkdir(parents=True, exist_ok=True)

    imported_files: list[dict[str, Any]] = []
    source_files = [normalize_game_rel_path(str(rel)) for rel in (source_manifest.get("files") or []) if str(rel or "").strip()]
    rebased_files = [_rebase_tyre_brand_ref(rel, old_id, target_id) for rel in source_files]
    for source_rel, target_rel in zip(source_files, rebased_files):
        source = _tyre_brand_package_member_path(package_root, source_rel, "file")
        target = target_dir / Path(*split_content_path(target_rel))
        assert_package_import_target(target, "tyre brand package file")
        target.parent.mkdir(parents=True, exist_ok=True)
        data = source.read_bytes()
        if target.suffix.lower() == ".tyretextures":
            data = _patch_tyre_brand_refs_in_data(data, old_id, target_id)
            safe_write_bytes(target, data)
        else:
            safe_write_bytes(target, data)
        imported_files.append({"sourceRel": source_rel, "rel": target_rel, "path": str(target), "bytes": len(data)})
    for row in package_manifest.get("sources") or []:
        if not isinstance(row, dict):
            continue
        kind = str(row.get("kind") or "").strip().lower()
        rel = str(row.get("path") or "").strip()
        if kind not in {"slick", "blur", "mark"} or not rel:
            continue
        source = _tyre_brand_package_member_path(package_root, rel, "source")
        if kind == "mark":
            source = _validate_square_png_source(source, "Brand mark PNG")
        target_name = {"slick": "source_slick.png", "blur": "source_blur.png", "mark": "mark.png"}[kind]
        copy_package_import_file(source, target_dir / target_name, "tyre brand package source PNG")

    clean_manifest = _clean_tyre_brand_package_manifest(source_manifest, target_id)
    for key in ("sourcePngOriginalPath", "sourceBlurPngOriginalPath", "markPngOriginalPath"):
        clean_manifest.pop(key, None)
    clean_manifest["id"] = target_id
    clean_manifest["tyretexturesRel"] = _rebase_tyre_brand_ref(str(source_manifest.get("tyretexturesRel") or ""), old_id, target_id)
    clean_manifest["refs"] = [_rebase_tyre_brand_ref(ref, old_id, target_id) for ref in (source_manifest.get("refs") or []) if str(ref or "").strip()]
    wet_ref = normalize_game_rel_path(RACING_GENERIC_WET_TYRETEXTURES_REF)
    if wet_ref not in clean_manifest["refs"]:
        clean_manifest["refs"].append(wet_ref)
    clean_manifest["fallback"] = _rebase_tyre_brand_ref(str(source_manifest.get("fallback") or RACING_GENERIC_SLICK_TYRETEXTURES_REF), old_id, target_id)
    clean_manifest["files"] = rebased_files
    if (target_dir / "mark.png").exists():
        clean_manifest["markPng"] = "mark.png"
    clean_manifest["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    manifest_path = target_dir / "manifest.json"
    assert_package_import_target(manifest_path, "tyre brand package manifest")
    safe_write_text(manifest_path, json.dumps(_json_ready(clean_manifest), indent=2, ensure_ascii=False))
    return {
        "packageRoot": str(package_root),
        "packagePath": str(source_package),
        "brand": {**clean_manifest, "manifestPath": str(manifest_path), "assetRoot": str(target_dir)},
        "brandId": target_id,
        "sourceBrandId": old_id,
        "importedFiles": imported_files,
        "importedFileCount": len(imported_files),
        "movedExisting": moved_existing,
        "conflictMode": "replace" if replace_existing else "rename",
    }


def inspect_custom_tyre_brand_usage(brand_id: str) -> dict[str, Any]:
    manifest = _custom_tyre_brand_manifest_by_id(brand_id)
    if not manifest:
        raise FileNotFoundError(f"Custom tyre brand not found: {brand_id}")
    tyre_ref = normalize_game_rel_path(str(manifest.get("tyretexturesRel") or "")).lower()
    refs = {tyre_ref} if tyre_ref else set()
    for ref in manifest.get("refs") or []:
        ref_norm = normalize_game_rel_path(str(ref or "")).lower()
        if ref_norm and "racing_generic" not in ref_norm:
            refs.add(ref_norm)
    used_by: list[dict[str, Any]] = []
    for entry in load_my_skins():
        game_root = normalize_game_root(str(entry.get("game_root") or get_default_root_folder() or Path.cwd()))
        files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
        visual_path = _skin_file_disk_path(game_root, entry, str(files.get("visual_preset") or ""))
        if not visual_path or not visual_path.exists():
            continue
        current_refs = _visualpreset_tyretexture_refs(visual_path.read_bytes())
        matched = [
            str(row.get("path") or "")
            for row in current_refs
            if normalize_game_rel_path(str(row.get("path") or "")).lower() in refs
        ]
        if not matched:
            continue
        used_by.append({
            "key": entry.get("key") or "",
            "displayName": entry.get("display_name") or entry.get("skin_id") or "",
            "skinId": entry.get("skin_id") or "",
            "carModel": entry.get("car_model") or "",
            "active": bool(entry.get("active")),
            "refs": matched,
            "visualPath": str(visual_path),
        })
    return {
        "brandId": str(manifest.get("id") or brand_id),
        "label": str(manifest.get("label") or manifest.get("brandName") or brand_id),
        "skinCount": len(used_by),
        "usedBySkins": _json_ready(used_by),
    }


def _mark_tyre_brand_users_update_required(brand_id: str, reason: str, workflow: str) -> dict[str, Any]:
    usage = inspect_custom_tyre_brand_usage(brand_id)
    if not usage.get("skinCount"):
        return {"updatedCount": 0, "updated": [], "usage": usage}
    skins = load_my_skins()
    keys = {str(row.get("key") or "") for row in usage.get("usedBySkins") or []}
    updated: list[dict[str, Any]] = []
    for entry in skins:
        key = str(entry.get("key") or "")
        if key not in keys:
            continue
        if bool(entry.get("active")):
            _mark_my_skin_game_sync_required(entry, reason, workflow)
            updated.append({"key": key, "displayName": entry.get("display_name") or entry.get("skin_id") or ""})
    save_my_skins(skins)
    return {"updatedCount": len(updated), "updated": updated, "usage": usage}


def save_custom_tyre_brand_template(root: str | Path, payload: dict[str, Any]) -> dict[str, Any]:
    data = payload if isinstance(payload, dict) else {}
    brand_id = str(data.get("id") or "").strip()
    label = str(data.get("brandName") or data.get("label") or "").strip()
    asset_id = str(data.get("assetId") or brand_id or label).strip()
    template_id = str(data.get("template") or "racing_cup_1").strip()
    slick_png = str(data.get("slickPng") or "").strip()
    blur_png = str(data.get("blurPng") or "").strip()
    mark_png = str(data.get("markPng") or "").strip()
    is_new = bool(data.get("isNew")) or not brand_id
    if is_new:
        if not label:
            raise ValueError("Brand name is required")
        if not slick_png or not blur_png:
            raise ValueError("Slick PNG and Blur PNG are required")
        return create_racing_tyre_brand(root, label, asset_id or label, slick_png, blur_png, template_id, overwrite=False, mark_png=mark_png)

    manifest = _custom_tyre_brand_manifest_by_id(brand_id)
    if not manifest:
        raise FileNotFoundError(f"Custom tyre brand not found: {brand_id}")
    game_root = normalize_game_root(root)
    label = label or str(manifest.get("label") or manifest.get("brandName") or brand_id)
    template_path = _racing_tyre_template_path(game_root, template_id)
    template_fields = _tyretextures_text_fields(template_path)
    template_c = resolve_content_path(game_root, template_fields.get(5, ""))
    template_blur_c = resolve_content_path(game_root, template_fields.get(9, ""))
    if not template_c.exists() or not template_blur_c.exists():
        raise FileNotFoundError("Template tyre color textures are missing")

    asset_root = Path(str(manifest.get("_assetRoot") or ""))
    if not asset_root.exists():
        raise FileNotFoundError(f"Tyre brand asset folder not found: {asset_root}")
    tyretextures_rel = normalize_game_rel_path(str(manifest.get("tyretexturesRel") or ""))
    if not tyretextures_rel:
        raise ValueError("Tyre brand manifest has no tyretexturesRel")
    tyre_id = Path(tyretextures_rel.replace("\\", "/")).parent.name
    color_rel = normalize_game_rel_path(str(Path("content") / "cars" / "common_assets" / "parts" / "tyres" / "textures" / tyre_id / f"tyre_{tyre_id}_c.texture"))
    blur_color_rel = normalize_game_rel_path(str(Path("content") / "cars" / "common_assets" / "parts" / "tyres" / "textures" / tyre_id / f"tyre_{tyre_id}_blur_c.texture"))
    target_tyretextures = asset_root / Path(*split_content_path(tyretextures_rel))
    target_color = asset_root / Path(*split_content_path(color_rel))
    target_blur_color = asset_root / Path(*split_content_path(blur_color_rel))
    slick_source = Path(slick_png) if slick_png else _custom_tyre_brand_slick_source_path(manifest)
    blur_source = Path(blur_png) if blur_png else _custom_tyre_brand_blur_source_path(manifest)
    if slick_source is None or not slick_source.exists():
        raise FileNotFoundError("Slick source PNG is missing")
    if blur_source is None or not blur_source.exists():
        raise FileNotFoundError("Blur source PNG is missing")

    color_result = _write_png_as_texturemips_for_template(slick_source, target_color, template_c)
    blur_result = _write_png_as_texturemips_for_template(blur_source, target_blur_color, template_blur_c)
    target_slick_source = asset_root / "source_slick.png"
    target_blur_source = asset_root / "source_blur.png"
    shutil.copy2(slick_source, target_slick_source)
    shutil.copy2(blur_source, target_blur_source)
    mark_name = _copy_optional_tyre_brand_mark_png(mark_png, asset_root, remove_when_empty="markPng" in data)
    replacements = {
        2: f"{label} Slick",
        5: color_rel,
        9: blur_color_rel,
        102: label,
    }
    safe_write_bytes(target_tyretextures, _patch_tyretextures_data(template_path.read_bytes(), replacements))
    files = [
        tyretextures_rel,
        color_rel,
        normalize_game_rel_path(str(Path(color_rel).with_suffix(".texturemips"))),
        blur_color_rel,
        normalize_game_rel_path(str(Path(blur_color_rel).with_suffix(".texturemips"))),
    ]
    manifest.update({
        "label": label,
        "brandName": label,
        "displayName": f"{label} Slick",
        "template": str(template_path.relative_to(game_root)).replace("/", "\\"),
        "tyretexturesRel": tyretextures_rel,
        "refs": [tyretextures_rel, RACING_GENERIC_WET_TYRETEXTURES_REF],
        "fallback": RACING_GENERIC_SLICK_TYRETEXTURES_REF,
        "files": files,
        "sourcePng": target_slick_source.name,
        "sourceBlurPng": target_blur_source.name,
        "sourcePngOriginalPath": str(Path(slick_source).expanduser().resolve()),
        "sourceBlurPngOriginalPath": str(Path(blur_source).expanduser().resolve()),
        "updatedAt": datetime.now().isoformat(timespec="seconds"),
    })
    if mark_name:
        manifest["markPng"] = mark_name
        manifest["markPngOriginalPath"] = str(Path(mark_png).expanduser().resolve())
    else:
        manifest.pop("markPng", None)
        manifest.pop("markPngOriginalPath", None)
    for transient in ("_manifestPath", "_assetRoot"):
        manifest.pop(transient, None)
    manifest_path = asset_root / "manifest.json"
    safe_write_text(manifest_path, json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False))
    affected = _mark_tyre_brand_users_update_required(brand_id, "Tyre brand template changed", "save_custom_tyre_brand_template")
    return {
        "item": _json_ready({**manifest, "manifestPath": str(manifest_path), "assetRoot": str(asset_root)}),
        "color": color_result,
        "blurColor": blur_result,
        "affectedSkins": affected,
        "root": str(get_tyre_brands_root()),
    }


def duplicate_custom_tyre_brand_template(brand_id: str) -> dict[str, Any]:
    manifest = _custom_tyre_brand_manifest_by_id(brand_id)
    if not manifest:
        raise FileNotFoundError(f"Custom tyre brand not found: {brand_id}")
    old_id = str(manifest.get("id") or brand_id).strip().lower()
    target_root = get_tyre_brands_root() / "racing"
    target_slug = _tyre_brand_unique_slug(target_root, f"{_tyre_brand_slug_from_id(old_id)}_copy")
    target_id = _tyre_brand_id_from_slug(target_slug)
    source_root = Path(str(manifest.get("_assetRoot") or ""))
    target_dir = target_root / target_slug
    target_dir.mkdir(parents=True, exist_ok=True)
    source_files = [normalize_game_rel_path(str(rel)) for rel in (manifest.get("files") or []) if str(rel or "").strip()]
    rebased_files = [_rebase_tyre_brand_ref(rel, old_id, target_id) for rel in source_files]
    for source_rel, target_rel in zip(source_files, rebased_files):
        source = source_root / Path(*split_content_path(source_rel))
        if not source.exists():
            raise FileNotFoundError(f"Custom tyre brand file missing: {source}")
        target = target_dir / Path(*split_content_path(target_rel))
        target.parent.mkdir(parents=True, exist_ok=True)
        data = source.read_bytes()
        if target.suffix.lower() == ".tyretextures":
            data = _patch_tyre_brand_refs_in_data(data, old_id, target_id)
        safe_write_bytes(target, data)
    for source_path, target_name in (
        (_custom_tyre_brand_slick_source_path(manifest), "source_slick.png"),
        (_custom_tyre_brand_blur_source_path(manifest), "source_blur.png"),
        (_custom_tyre_brand_mark_source_path(manifest), "mark.png"),
    ):
        if source_path and source_path.exists():
            shutil.copy2(source_path, target_dir / target_name)
    label = str(manifest.get("label") or manifest.get("brandName") or old_id).strip()
    next_manifest = _clean_tyre_brand_package_manifest(manifest, target_id)
    next_manifest.update({
        "id": target_id,
        "label": f"{label} Copy",
        "brandName": f"{label} Copy",
        "displayName": f"{label} Copy Slick",
        "tyretexturesRel": _rebase_tyre_brand_ref(str(manifest.get("tyretexturesRel") or ""), old_id, target_id),
        "refs": [_rebase_tyre_brand_ref(ref, old_id, target_id) for ref in (manifest.get("refs") or []) if str(ref or "").strip()],
        "files": rebased_files,
        "sourcePng": "source_slick.png",
        "sourceBlurPng": "source_blur.png",
        "sourcePngOriginalPath": str(manifest.get("sourcePngOriginalPath") or ""),
        "sourceBlurPngOriginalPath": str(manifest.get("sourceBlurPngOriginalPath") or ""),
        "createdAt": datetime.now().isoformat(timespec="seconds"),
        "updatedAt": datetime.now().isoformat(timespec="seconds"),
    })
    if (target_dir / "mark.png").exists():
        next_manifest["markPng"] = "mark.png"
        next_manifest["markPngOriginalPath"] = str(manifest.get("markPngOriginalPath") or "")
    wet_ref = normalize_game_rel_path(RACING_GENERIC_WET_TYRETEXTURES_REF)
    if wet_ref not in next_manifest["refs"]:
        next_manifest["refs"].append(wet_ref)
    manifest_path = target_dir / "manifest.json"
    safe_write_text(manifest_path, json.dumps(_json_ready(next_manifest), indent=2, ensure_ascii=False))
    return {"item": _json_ready({**next_manifest, "manifestPath": str(manifest_path), "assetRoot": str(target_dir)}), "brandId": target_id}


def delete_custom_tyre_brand_template(brand_id: str, reset_users: bool = True) -> dict[str, Any]:
    manifest = _custom_tyre_brand_manifest_by_id(brand_id)
    if not manifest:
        raise FileNotFoundError(f"Custom tyre brand not found: {brand_id}")
    usage = inspect_custom_tyre_brand_usage(brand_id)
    skins = load_my_skins()
    used_keys = {str(row.get("key") or "") for row in usage.get("usedBySkins") or []}
    reset: list[dict[str, Any]] = []
    if reset_users and used_keys:
        for entry in skins:
            key = str(entry.get("key") or "")
            if key not in used_keys:
                continue
            game_root = normalize_game_root(str(entry.get("game_root") or get_default_root_folder() or Path.cwd()))
            files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
            visual_rel = str(files.get("visual_preset") or "").strip()
            source_visual_path = _skin_file_disk_path(game_root, entry, visual_rel)
            if source_visual_path is None or not source_visual_path.exists():
                continue
            visual_path = _ensure_local_skin_file_copy(game_root, entry, visual_rel, source=source_visual_path)
            if visual_path is None or not visual_path.exists():
                continue
            old_data = visual_path.read_bytes()
            new_data = edit_visualpreset_tyre_brand_data(old_data, "default", [])
            if new_data != old_data:
                safe_write_bytes(visual_path, new_data)
                if bool(entry.get("active")):
                    _mark_my_skin_game_sync_required(entry, "Tyre brand template deleted", "delete_custom_tyre_brand_template")
                reset.append({"key": key, "displayName": entry.get("display_name") or entry.get("skin_id") or ""})
            default_info = describe_my_skin_tyre_brand(entry, game_root)
            entry["tyre_brand"] = _json_ready(
                _persisted_tyre_brand_selection(
                    default_info,
                    "default",
                    list(default_info.get("availableOptions") or []),
                )
            )
        save_my_skins(skins)
    asset_root = Path(str(manifest.get("_assetRoot") or ""))
    trash_dir = _numberplate_package_move_existing(asset_root) if asset_root.exists() else ""
    return {
        "brandId": str(manifest.get("id") or brand_id),
        "label": str(manifest.get("label") or manifest.get("brandName") or brand_id),
        "usage": usage,
        "resetSkins": reset,
        "resetCount": len(reset),
        "trashDir": trash_dir,
    }


def create_racing_tyre_brand(root: str | Path, brand_name: str, asset_id: str, slick_png: str | Path, blur_png: str | Path, template_id: str = "racing_cup_1", overwrite: bool = False, mark_png: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    label = str(brand_name or "").strip()
    if not label:
        raise ValueError("Brand name is required")
    clean_slug = _tyre_brand_slug_from_id(asset_id or label, label)
    tyre_id = f"racing_{clean_slug}_1"
    slick_source = Path(slick_png)
    blur_source = Path(blur_png)
    if not slick_source.exists():
        raise FileNotFoundError(f"Slick tyre PNG not found: {slick_source}")
    if not blur_source.exists():
        raise FileNotFoundError(f"Blur tyre PNG not found: {blur_source}")

    template_path = _racing_tyre_template_path(game_root, template_id)
    template_fields = _tyretextures_text_fields(template_path)
    template_c = resolve_content_path(game_root, template_fields.get(5, ""))
    template_blur_c = resolve_content_path(game_root, template_fields.get(9, ""))
    if not template_c.exists() or not template_blur_c.exists():
        raise FileNotFoundError("Template tyre color textures are missing")

    asset_root = get_tyre_brands_root() / "racing" / clean_slug
    content_prefix = Path("content") / "cars" / "common_assets" / "parts" / "tyres" / "textures" / tyre_id
    target_dir = asset_root / content_prefix
    manifest_path = asset_root / "manifest.json"
    if manifest_path.exists() and not overwrite:
        raise FileExistsError(f"Tyre brand already exists: {tyre_id}")
    target_dir.mkdir(parents=True, exist_ok=True)

    tyretextures_rel = normalize_game_rel_path(str(content_prefix / f"tyre_{tyre_id}.tyretextures"))
    color_rel = normalize_game_rel_path(str(content_prefix / f"tyre_{tyre_id}_c.texture"))
    blur_color_rel = normalize_game_rel_path(str(content_prefix / f"tyre_{tyre_id}_blur_c.texture"))
    target_tyretextures = asset_root / Path(*split_content_path(tyretextures_rel))
    target_color = asset_root / Path(*split_content_path(color_rel))
    target_blur_color = asset_root / Path(*split_content_path(blur_color_rel))
    target_slick_source = asset_root / "source_slick.png"
    target_blur_source = asset_root / "source_blur.png"
    target_mark_source = asset_root / "mark.png"

    color_result = _write_png_as_texturemips_for_template(slick_source, target_color, template_c)
    blur_result = _write_png_as_texturemips_for_template(blur_source, target_blur_color, template_blur_c)
    shutil.copy2(slick_source, target_slick_source)
    shutil.copy2(blur_source, target_blur_source)
    mark_name = _copy_optional_tyre_brand_mark_png(mark_png, asset_root)
    replacements = {
        2: f"{label} Slick",
        5: color_rel,
        9: blur_color_rel,
        102: label,
    }
    safe_write_bytes(target_tyretextures, _patch_tyretextures_data(template_path.read_bytes(), replacements))

    files = [
        tyretextures_rel,
        color_rel,
        normalize_game_rel_path(str(Path(color_rel).with_suffix(".texturemips"))),
        blur_color_rel,
        normalize_game_rel_path(str(Path(blur_color_rel).with_suffix(".texturemips"))),
    ]
    manifest = {
        "formatVersion": 1,
        "type": "liverylab_custom_tyre_brand",
        "family": "racing",
        "id": tyre_id,
        "label": label,
        "brandName": label,
        "displayName": f"{label} Slick",
        "template": str(template_path.relative_to(game_root)).replace("/", "\\"),
        "tyretexturesRel": tyretextures_rel,
        "refs": [tyretextures_rel, RACING_GENERIC_WET_TYRETEXTURES_REF],
        "fallback": RACING_GENERIC_SLICK_TYRETEXTURES_REF,
        "files": files,
        "sourcePng": target_slick_source.name,
        "sourceBlurPng": target_blur_source.name,
        "sourcePngOriginalPath": str(slick_source.expanduser().resolve()),
        "sourceBlurPngOriginalPath": str(blur_source.expanduser().resolve()),
        "createdAt": datetime.now().isoformat(timespec="seconds"),
        "updatedAt": datetime.now().isoformat(timespec="seconds"),
    }
    if mark_name:
        manifest["markPng"] = target_mark_source.name
        manifest["markPngOriginalPath"] = str(Path(mark_png).expanduser().resolve())
    safe_write_text(manifest_path, json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False))
    return {
        "item": _json_ready({**manifest, "manifestPath": str(manifest_path), "assetRoot": str(asset_root)}),
        "color": color_result,
        "blurColor": blur_result,
        "root": str(get_tyre_brands_root()),
    }


def _custom_tyre_manifest_for_ref(ref: str) -> dict[str, Any] | None:
    needle = normalize_game_rel_path(str(ref or "")).lower()
    if not needle:
        return None
    for manifest_path in _custom_tyre_brand_manifest_paths():
        manifest = _load_custom_tyre_brand_manifest(manifest_path)
        if not manifest:
            continue
        refs = [
            normalize_game_rel_path(str(row or "")).lower()
            for row in (manifest.get("refs") or [])
            if "racing_generic" not in normalize_game_rel_path(str(row or "")).lower()
        ]
        tyre_ref = normalize_game_rel_path(str(manifest.get("tyretexturesRel") or "")).lower()
        if needle == tyre_ref or needle in refs:
            return manifest
    return None


def _sync_custom_tyre_brand_assets_for_entry(entry: dict[str, Any], game_root: Path, writer: GameFileWriter) -> list[str]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    visual_rel = str(files.get("visual_preset") or "").strip()
    visual_path = _skin_file_disk_path(game_root, entry, visual_rel)
    if visual_path is None or not visual_path.exists():
        return []
    refs = _visualpreset_tyretexture_refs(visual_path.read_bytes())
    manifests: dict[str, dict[str, Any]] = {}
    for row in refs:
        manifest = _custom_tyre_manifest_for_ref(str(row.get("path") or ""))
        if manifest:
            manifests[str(manifest.get("id") or manifest.get("_manifestPath"))] = manifest
    actions: list[str] = []
    for manifest in manifests.values():
        asset_root = Path(str(manifest.get("_assetRoot") or ""))
        copied = 0
        for rel in manifest.get("files") or []:
            rel_norm = normalize_game_rel_path(str(rel or ""))
            if not rel_norm:
                continue
            source = asset_root / Path(*split_content_path(rel_norm))
            if not source.exists():
                raise FileNotFoundError(f"Custom tyre brand file missing: {source}")
            target = game_root / Path(*split_content_path(rel_norm))
            writer.copy_file(source, target, label="activate custom tyre brand")
            copied += 1
        actions.append(f"custom tyre brand synced: {manifest.get('label') or manifest.get('id')} ({copied} files)")
    return actions


def _wheel_transform(label: str, layout: dict[str, Any], radius: float | None = None) -> dict[str, float]:
    wheelbase = float(layout.get("wheelbase") or 2.85)
    wheelbase += float(layout.get("wheelbaseAdjust") or 0.0)
    front_track = float(layout.get("frontTrack") or 1.72)
    rear_track = float(layout.get("rearTrack") or front_track)
    front_track += float(layout.get("frontTrackAdjust") or 0.0)
    rear_track += float(layout.get("rearTrackAdjust") or 0.0)
    is_front = label.endswith("_lf") or label.endswith("_rf")
    is_left = label.endswith("_lf") or label.endswith("_lr")
    track = front_track if is_front else rear_track
    y_offset = float(layout.get("wheelYOffset") or 0.0)
    y_offset += float(layout.get("frontWheelYOffset") or 0.0) if is_front else float(layout.get("rearWheelYOffset") or 0.0)
    z_offset = float(layout.get("frontWheelZOffset") or 0.0) if is_front else float(layout.get("rearWheelZOffset") or 0.0)
    transform = {
        "x": (-track * 0.5) if is_left else (track * 0.5),
        "y": float(radius if radius is not None else 0.34) + y_offset,
        "z": ((wheelbase * 0.5) if is_front else (-wheelbase * 0.5)) + z_offset,
        "rx": 0.0,
        "ry": 0.0,
        "rz": 0.0,
        "sx": 1.0,
        "sy": 1.0,
        "sz": 1.0,
    }
    overrides = layout.get("wheelOverrides") if isinstance(layout.get("wheelOverrides"), dict) else {}
    override = overrides.get(label) if isinstance(overrides.get(label), dict) else {}
    for axis in ("x", "y", "z"):
        try:
            transform[axis] += float(override.get(axis) or 0.0)
        except (TypeError, ValueError):
            pass
    return transform


def _resolve_livery_preview_wheel_meshes(entry: dict[str, Any], game_root: Path, apply_calibrations: bool = True) -> dict[str, Any]:
    files = dict(entry.get("files") or {})
    cardesign_rel = str(files.get("cardesign") or "").strip()
    car_content_path = norm_slashes(str(entry.get("car_content_path") or f"content\\cars\\{entry.get('car_model') or ''}").strip())
    layout = _preview_wheel_layout(game_root, car_content_path)
    car_model = str(entry.get("car_model") or "").strip().lower()
    car_from_content = split_content_path(car_content_path)[-1].lower() if car_content_path else ""
    if apply_calibrations:
        _apply_wheel_calibration(layout, car_model)
        if not layout.get("calibration") and car_from_content and car_from_content != car_model:
            _apply_wheel_calibration(layout, car_from_content)
    result: dict[str, Any] = {
        "cardesign": cardesign_rel,
        "cardesignLocal": "",
        "cardesignSource": "",
        "compatiblerim": "",
        "rimmesh": "",
        "rimmeshLocal": "",
        "layout": layout,
        "meshRefs": [],
        "warnings": [],
    }
    if not cardesign_rel:
        result["warnings"].append("Skin entry has no .cardesign reference.")
        return result

    local_cardesign_path = _local_skin_asset_path(entry, cardesign_rel)
    cardesign_path = local_cardesign_path if local_cardesign_path and local_cardesign_path.exists() else resolve_content_path(game_root, cardesign_rel)
    if not cardesign_path.exists():
        result["warnings"].append(f"Cardesign not found: {cardesign_rel}")
        return result
    result["cardesignLocal"] = str(cardesign_path)
    result["cardesignSource"] = "local" if local_cardesign_path and cardesign_path == local_cardesign_path else "game"

    details = extract_cardesign_details(cardesign_path.read_bytes(), root=game_root)
    rim_ref = str(details.get("rim_path") or "").strip()
    result["compatiblerim"] = rim_ref
    if not rim_ref:
        result["warnings"].append("Cardesign has no compatible rim reference.")
        return result

    rim_path = resolve_content_path(game_root, rim_ref)
    rimmesh_refs: list[str] = []
    compatibletyres_refs: list[str] = []
    if rim_path.exists() and rim_path.suffix.lower() == ".rimmesh":
        rimmesh_refs = [content_rel_from_disk(game_root, rim_path) or rim_ref]
    elif rim_path.exists() and rim_path.suffix.lower() == ".compatiblerim":
        compat_data = rim_path.read_bytes()
        rimmesh_refs = _compatiblerim_rimmesh_refs(compat_data)
        compatibletyres_refs = _compatiblerim_compatibletyres_refs(compat_data)
    else:
        result["warnings"].append(f"Compatible rim file not found: {rim_ref}")
        return result
    tyre_texture_set_ref = _preview_tyre_texture_set_ref(game_root, rim_path if rim_path.exists() else None, compatibletyres_refs)
    visual_tyre_info = _visualpreset_tyre_brand_info(_skin_file_disk_path(game_root, entry, str(files.get("visual_preset") or "")))
    visual_tyre_refs = list(visual_tyre_info.get("refs") or [])
    if visual_tyre_refs:
        tyre_texture_set_ref = str(visual_tyre_refs[0] or "") or tyre_texture_set_ref
    result["compatibletyres"] = compatibletyres_refs
    result["tyreTextureSet"] = tyre_texture_set_ref or ""
    result["tyreBrand"] = visual_tyre_info

    if not rimmesh_refs:
        result["warnings"].append(f"No .rimmesh reference found in: {rim_ref}")
        return result

    rimmesh_ref = rimmesh_refs[0]
    front_rimmesh_path = resolve_content_path(game_root, rimmesh_ref)
    rear_rimmesh_ref = rimmesh_refs[1] if len(rimmesh_refs) > 1 else rimmesh_ref
    rear_rimmesh_path = resolve_content_path(game_root, rear_rimmesh_ref)
    result["rimmesh"] = rimmesh_ref
    result["rimmeshLocal"] = str(front_rimmesh_path)
    result["rearRimmesh"] = rear_rimmesh_ref
    result["rearRimmeshLocal"] = str(rear_rimmesh_path)
    if not front_rimmesh_path.exists():
        result["warnings"].append(f"Rimmesh not found: {rimmesh_ref}")
        return result
    if not rear_rimmesh_path.exists():
        result["warnings"].append(f"Rear rimmesh not found: {rear_rimmesh_ref}")
        return result

    rim = inspect_rimmesh_readable(front_rimmesh_path, root=game_root)
    rear_rim = inspect_rimmesh_readable(rear_rimmesh_path, root=game_root)
    result["rimmeshInfo"] = {
        "front": {
            "diameter": rim.get("diameter"),
            "width": rim.get("width"),
            "offset": rim.get("offset"),
            "name": rim.get("name"),
            "brand": rim.get("brand"),
        },
        "rear": {
            "diameter": rear_rim.get("diameter"),
            "width": rear_rim.get("width"),
            "offset": rear_rim.get("offset"),
            "name": rear_rim.get("name"),
            "brand": rear_rim.get("brand"),
        },
        "name": rim.get("name"),
        "brand": rim.get("brand"),
    }
    # ACE wheel meshes are side-specific, but in the preview coordinate frame their
    # visible rim faces are outward when left/right mesh references are crossed.
    rim_refs: list[tuple[str, str]] = [
        ("wheel_lf", rim.get("mesh_rf")),
        ("wheel_rf", rim.get("mesh_lf")),
    ]
    rim_refs.extend([
        ("wheel_lr", rear_rim.get("mesh_rf")),
        ("wheel_rr", rear_rim.get("mesh_lf")),
    ])

    front_tyre_ref = str(rim.get("tyre_mesh") or "").strip()
    rear_tyre_ref = str(rear_rim.get("tyre_mesh") or front_tyre_ref).strip()
    wheel_overrides = layout.get("wheelOverrides") if isinstance(layout.get("wheelOverrides"), dict) else {}
    for label, rel in rim_refs:
        rel_text = str(rel or "").strip()
        if not rel_text:
            continue
        resolved_rel, mesh_path, source_rel = _resolve_preview_mesh_ref(game_root, rel_text)
        calibration_override = wheel_overrides.get(label) if isinstance(wheel_overrides.get(label), dict) else {}
        result["meshRefs"].append(
            {
                "label": label,
                "relPath": resolved_rel,
                "sourceRelPath": source_rel,
                "path": str(mesh_path),
                "exists": mesh_path.exists(),
                "calibrationOverride": calibration_override,
                "transform": _wheel_transform(label, layout),
            }
        )
    for tyre_ref, labels in [
        (front_tyre_ref, ("tyre_lf", "tyre_rf")),
        (rear_tyre_ref, ("tyre_lr", "tyre_rr")),
    ]:
        if not tyre_ref:
            continue
        resolved_rel, mesh_path, source_rel = _resolve_preview_mesh_ref(game_root, tyre_ref)
        for label in labels:
            calibration_override = wheel_overrides.get(label) if isinstance(wheel_overrides.get(label), dict) else {}
            result["meshRefs"].append(
                {
                    "label": label,
                    "relPath": resolved_rel,
                    "sourceRelPath": source_rel,
                    "path": str(mesh_path),
                    "exists": mesh_path.exists(),
                    "tyreTextureSet": tyre_texture_set_ref,
                    "calibrationOverride": calibration_override,
                    "transform": _wheel_transform(label, layout),
                }
            )
    missing = [row for row in result["meshRefs"] if not row.get("exists")]
    if missing:
        result["warnings"].extend([f"Wheel mesh not found: {row.get('sourceRelPath') or row.get('relPath')}" for row in missing])
    return result


def build_livery_preview_model(
    skin_key: str,
    root: str | Path | None = None,
    force: bool = False,
    apply_wheel_calibrations: bool = True,
) -> dict[str, Any]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    prepared = prepare_livery_preview_project(skin_key, root)
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    if (
        project_types.project_type_for_entry(entry) == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY
        and official_external_workflow.role_for_entry(entry)
        in {official_external_workflow.ROLE_PROJECT, official_external_workflow.ROLE_LIBRARY}
    ):
        glb_path = Path(str(project.get("modelPath") or project.get("modelCachePath") or ""))
        if not glb_path.exists() or not glb_path.is_file():
            raise FileNotFoundError(f"Official preview GLB not found: {glb_path}")
        preview_assets = prepared.get("previewAssets") if isinstance(prepared.get("previewAssets"), dict) else {}
        glb = {
            "glbPath": str(glb_path),
            "sizeBytes": glb_path.stat().st_size,
            "cached": True,
            "materialName": project.get("materialName") or "EXT_SKIN",
        }
        if isinstance(preview_assets.get("glb"), dict):
            glb.update({key: value for key, value in preview_assets["glb"].items() if key not in {"path", "name", "exists", "size", "mtimeMs"}})
        variant_part_preview = project.get("variantPartPreview") if isinstance(project.get("variantPartPreview"), dict) else {}
        official_assets = project.get("officialPreviewAssets") if isinstance(project.get("officialPreviewAssets"), dict) else {}
        manifest_path = Path(str(official_assets.get("manifestPath") or prepared.get("manifestPath") or ""))
        if manifest_path.exists() and manifest_path.is_file():
            try:
                manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
            except Exception:
                manifest = {}
            if isinstance(manifest, dict):
                manifest_glb = manifest.get("glb") if isinstance(manifest.get("glb"), dict) else {}
                manifest_materials = manifest_glb.get("materials") if isinstance(manifest_glb.get("materials"), list) else manifest.get("materials")
                manifest_components = manifest_glb.get("componentMaterials") if isinstance(manifest_glb.get("componentMaterials"), list) else manifest.get("componentMaterials")
                if not isinstance(glb.get("materials"), list) and isinstance(manifest_materials, list):
                    glb["materials"] = manifest_materials
                if not isinstance(glb.get("componentMaterials"), list) and isinstance(manifest_components, list):
                    glb["componentMaterials"] = manifest_components
        _apply_variant_hidden_exterior_materials(glb, project, variant_part_preview)
        auxiliary_models = project.get("auxiliaryModels") if isinstance(project.get("auxiliaryModels"), list) else []
        return {
            **prepared,
            "project": project,
            "glb": glb,
            "wheelPreview": {"auxiliaryModels": [], "warnings": ["Official preview uses car-level Derived GLB; wheel auxiliary cache is skipped."]},
            "variantPartPreview": _json_ready(variant_part_preview),
            "modelCache": {
                "path": str(glb_path),
                "metaPath": "",
                "exists": glb_path.exists(),
                "cached": True,
            },
            "auxiliaryModels": _json_ready(auxiliary_models),
        }
    mesh_path = Path(str(project.get("exteriorMeshPath") or ""))
    glb_path = Path(str(project.get("modelCachePath") or ""))
    meta_path = Path(str(project.get("modelMetaPath") or ""))
    if not mesh_path.exists():
        raise FileNotFoundError(f"Exterior mesh not found: {mesh_path}")
    if not glb_path:
        raise ValueError("Model cache path is empty")

    mesh_stat = mesh_path.stat()
    source_mesh_rel = str(project.get("exteriorMeshRelPath") or "")
    cache_valid = False
    if glb_path.exists() and meta_path.exists() and not force:
        try:
            meta = json.loads(meta_path.read_text(encoding="utf-8"))
        except Exception:
            meta = {}
        stored_source_rel = str(meta.get("sourceMeshRelPath") or "").strip()
        stored_source_path = str(meta.get("sourceMeshPath") or "").strip()
        cache_valid = (
            (stored_source_rel == source_mesh_rel or stored_source_path == str(mesh_path))
            and int(meta.get("sourceMeshSize") or -1) == int(mesh_stat.st_size)
            and int(meta.get("sourceMeshMtimeMs") or -1) == int(mesh_stat.st_mtime * 1000)
            and int(meta.get("exporterVersion") or 0) == int(preview3d.GLB_EXPORTER_VERSION)
            and str(meta.get("materialName") or "") == str(project.get("materialName") or "EXT_SKIN")
        )
    else:
        meta = {}

    if cache_valid:
        glb = {
            "glbPath": str(glb_path),
            "sizeBytes": glb_path.stat().st_size,
            "cached": True,
            "meshName": meta.get("meshName"),
            "materialName": meta.get("materialName"),
            "materials": meta.get("materials"),
            "componentMaterials": meta.get("componentMaterials"),
            "vertexCount": meta.get("vertexCount"),
            "indexCount": meta.get("indexCount"),
            "triangleCount": meta.get("triangleCount"),
            "hasNormals": meta.get("hasNormals"),
            "hasUvs": meta.get("hasUvs"),
        }
    else:
        mesh_info = inspect_mesh_readable(mesh_path, root=root or project.get("gameRoot") or None)
        mesh_info["component_split_materials"] = [
            "EXT_SKIN",
            "EXT_DETAILS_CHROME",
            "EXT_DETAILS_PLASTIC",
            "EXT_DETAILS_PLASTIC_GLOSS",
            "EXT_DETAILS_RUBBER",
        ]
        mesh_info["minimum_component_triangles"] = 1
        glb = preview3d.write_mesh_info_glb(mesh_info, glb_path, str(project.get("materialName") or "EXT_SKIN"))
        glb["cached"] = False
        meta = {
            **glb,
            "sourceMeshRelPath": source_mesh_rel,
            "sourceMeshSize": int(mesh_stat.st_size),
            "sourceMeshMtimeMs": int(mesh_stat.st_mtime * 1000),
            "updatedAt": datetime.now().isoformat(timespec="seconds"),
        }
        meta_path.parent.mkdir(parents=True, exist_ok=True)
        meta_path.write_text(json.dumps(meta, indent=2, ensure_ascii=False), encoding="utf-8")

    wheel_preview: dict[str, Any] = {"auxiliaryModels": [], "warnings": []}
    try:
        game_root = normalize_game_root(root or project.get("gameRoot") or entry.get("game_root") or get_default_root_folder())
        wheel_preview = _resolve_livery_preview_wheel_meshes(entry, game_root, apply_calibrations=apply_wheel_calibrations)
        models_dir = glb_path.parent
        auxiliary_models: list[dict[str, Any]] = []
        for mesh_ref in wheel_preview.get("meshRefs", []):
            mesh_file = Path(str(mesh_ref.get("path") or ""))
            if not mesh_file.exists():
                continue
            label = preview3d.safe_segment(mesh_ref.get("label"), "wheel")
            aux_glb_path = _preview_aux_model_cache_path(models_dir, label, mesh_file)
            try:
                aux = _write_preview_aux_model(mesh_file, aux_glb_path, game_root, force=force)
                aux["label"] = label
                aux["kind"] = "wheel"
                aux["sourceRelPath"] = mesh_ref.get("relPath")
                aux["sourceOriginalRelPath"] = mesh_ref.get("sourceRelPath")
                aux["calibrationOverride"] = mesh_ref.get("calibrationOverride") if isinstance(mesh_ref.get("calibrationOverride"), dict) else {}
                if mesh_ref.get("tyreTextureSet"):
                    aux["tyreTextureSet"] = mesh_ref.get("tyreTextureSet")
                aux["transform"] = mesh_ref.get("transform")
                auxiliary_models.append(aux)
            except Exception as exc:
                wheel_preview.setdefault("warnings", []).append(f"Could not export {mesh_ref.get('label')}: {exc}")
        wheel_preview["auxiliaryModels"] = auxiliary_models
        project["auxiliaryModelPaths"] = [str(row.get("path")) for row in auxiliary_models if row.get("path")]
        project["auxiliaryModels"] = auxiliary_models
    except Exception as exc:
        wheel_preview = {"auxiliaryModels": [], "warnings": [f"Wheel preview probe failed: {exc}"]}
        project["auxiliaryModelPaths"] = []

    variant_part_preview = prepared.get("variantPartPreview") if isinstance(prepared.get("variantPartPreview"), dict) else {}
    variant_part_models: list[dict[str, Any]] = []
    if variant_part_preview.get("meshRefs"):
        try:
            game_root = normalize_game_root(root or project.get("gameRoot") or entry.get("game_root") or get_default_root_folder())
            models_dir = glb_path.parent
            for mesh_ref in variant_part_preview.get("meshRefs", []) or []:
                if not mesh_ref.get("previewRelevant"):
                    continue
                mesh_file = Path(str(mesh_ref.get("path") or ""))
                if not mesh_file.exists():
                    continue
                label = f"part_{preview3d.safe_segment(mesh_ref.get('label'), 'variant')}"
                aux_glb_path = _preview_variant_part_model_cache_path(models_dir, label, mesh_file)
                try:
                    aux = _write_preview_aux_model(mesh_file, aux_glb_path, game_root, force=force)
                    aux["label"] = label
                    aux["kind"] = "variantPart"
                    aux["sourceRelPath"] = mesh_ref.get("relPath")
                    aux["compatiblePartRel"] = mesh_ref.get("compatiblePartRel")
                    aux["carPartRel"] = mesh_ref.get("carPartRel")
                    aux["skinRelevant"] = bool(mesh_ref.get("skinRelevant"))
                    aux["skinTriangleCount"] = int(mesh_ref.get("skinTriangleCount") or 0)
                    aux["transform"] = mesh_ref.get("transform") if isinstance(mesh_ref.get("transform"), dict) else _variant_part_preview_transform(entry, str(mesh_ref.get("relPath") or ""))
                    variant_part_models.append(aux)
                except Exception as exc:
                    variant_part_preview.setdefault("warnings", []).append(f"Could not export {mesh_ref.get('label')}: {exc}")
            if variant_part_models:
                existing_auxiliary = [row for row in (project.get("auxiliaryModels") or []) if isinstance(row, dict)]
                project["auxiliaryModels"] = [*existing_auxiliary, *variant_part_models]
                project["auxiliaryModelPaths"] = [str(row.get("path")) for row in project["auxiliaryModels"] if row.get("path")]
            variant_part_preview["auxiliaryModels"] = variant_part_models
            project["variantPartPreview"] = _json_ready(variant_part_preview)
        except Exception as exc:
            variant_part_preview.setdefault("warnings", []).append(f"Variant part preview export failed: {exc}")
            project["variantPartPreview"] = _json_ready(variant_part_preview)

    _apply_variant_hidden_exterior_materials(glb, project, variant_part_preview)

    return {
        **prepared,
        "project": project,
        "glb": glb,
        "wheelPreview": wheel_preview,
        "variantPartPreview": _json_ready(variant_part_preview),
        "modelCache": {
            "path": str(glb_path),
            "metaPath": str(meta_path),
            "exists": glb_path.exists(),
            "cached": bool(glb.get("cached")),
        },
    }


def prepare_layer_studio_workspace(
    skin_key: str,
    root: str | Path | None = None,
    force: bool = False,
    apply_wheel_calibrations: bool = True,
    surface_key: str | None = None,
) -> dict[str, Any]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canUseEditablePreviewTools")
    preview_build = build_livery_preview_model(
        skin_key,
        root,
        force=force,
        apply_wheel_calibrations=apply_wheel_calibrations,
    )
    return {
        **preview_build,
        "layerStudio": layer_studio.workspace_context(preview_build, get_livery_library_root(), surface_key),
    }


def import_layer_studio_png(
    skin_key: str,
    source_png: str | Path,
    root: str | Path | None = None,
    placement: dict[str, Any] | None = None,
    surface_key: str | None = None,
    layer_document: dict[str, Any] | None = None,
) -> dict[str, Any]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canUseEditablePreviewTools")
    prepared = prepare_livery_preview_project(skin_key, root)
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    project_folder = str(project.get("projectFolder") or "").strip()
    if not project_folder:
        raise ValueError("Layer Studio needs a LiveryProjects workspace folder")
    context = layer_studio.workspace_context({"project": project}, get_livery_library_root(), surface_key)
    result = layer_studio.import_png_asset(
        project_folder,
        source_png,
        context.get("textureSize") if isinstance(context.get("textureSize"), dict) else {"width": layer_studio.TEXTURE_SIZE, "height": layer_studio.TEXTURE_SIZE},
        placement,
        context.get("activeSurfaceKey") or surface_key,
        layer_document,
    )
    return {
        **result,
        "skinKey": skin_key,
        "project": project,
        "layerStudio": layer_studio.workspace_context({"project": project}, get_livery_library_root(), result.get("surfaceKey") or surface_key),
    }


def list_layer_studio_asset_library() -> dict[str, Any]:
    return layer_studio.load_asset_library(get_livery_library_root())


def _layer_studio_project_context(
    skin_key: str,
    root: str | Path | None = None,
    surface_key: str | None = None,
) -> tuple[dict[str, Any], dict[str, Any], str]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canUseEditablePreviewTools")
    prepared = prepare_livery_preview_project(skin_key, root)
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    project_folder = str(project.get("projectFolder") or "").strip()
    if not project_folder:
        raise ValueError("Layer Studio needs a LiveryProjects workspace folder")
    context = layer_studio.workspace_context({"project": project}, get_livery_library_root(), surface_key)
    return project, context, project_folder


def list_layer_studio_project_asset_library(skin_key: str, root: str | Path | None = None) -> dict[str, Any]:
    _project, _context, project_folder = _layer_studio_project_context(skin_key, root)
    return layer_studio.load_project_asset_library(project_folder)


def add_layer_studio_library_png(source_png: str | Path) -> dict[str, Any]:
    return layer_studio.add_png_to_library(get_livery_library_root(), source_png)


def add_layer_studio_project_library_png(
    skin_key: str,
    source_png: str | Path,
    root: str | Path | None = None,
) -> dict[str, Any]:
    _project, _context, project_folder = _layer_studio_project_context(skin_key, root)
    return layer_studio.add_png_to_project_library(project_folder, source_png)


def create_layer_studio_library_group(name: str) -> dict[str, Any]:
    return layer_studio.create_library_group(get_livery_library_root(), name)


def create_layer_studio_project_library_group(skin_key: str, name: str, root: str | Path | None = None) -> dict[str, Any]:
    _project, _context, project_folder = _layer_studio_project_context(skin_key, root)
    return layer_studio.create_project_library_group(project_folder, name)


def update_layer_studio_library_group(group_id: str, patch: dict[str, Any]) -> dict[str, Any]:
    return layer_studio.update_library_group(get_livery_library_root(), group_id, patch)


def update_layer_studio_project_library_group(
    skin_key: str,
    group_id: str,
    patch: dict[str, Any],
    root: str | Path | None = None,
) -> dict[str, Any]:
    _project, _context, project_folder = _layer_studio_project_context(skin_key, root)
    return layer_studio.update_project_library_group(project_folder, group_id, patch)


def delete_layer_studio_library_group(group_id: str) -> dict[str, Any]:
    return layer_studio.delete_library_group(get_livery_library_root(), group_id)


def delete_layer_studio_project_library_group(skin_key: str, group_id: str, root: str | Path | None = None) -> dict[str, Any]:
    _project, _context, project_folder = _layer_studio_project_context(skin_key, root)
    return layer_studio.delete_project_library_group(project_folder, group_id)


def move_layer_studio_library_asset(asset_id: str, group_id: str) -> dict[str, Any]:
    return layer_studio.move_library_asset(get_livery_library_root(), asset_id, group_id)


def move_layer_studio_project_library_asset(
    skin_key: str,
    asset_id: str,
    group_id: str,
    root: str | Path | None = None,
) -> dict[str, Any]:
    _project, _context, project_folder = _layer_studio_project_context(skin_key, root)
    return layer_studio.move_project_library_asset(project_folder, asset_id, group_id)


def delete_layer_studio_library_asset(asset_id: str) -> dict[str, Any]:
    return layer_studio.delete_library_asset(get_livery_library_root(), asset_id)


def delete_layer_studio_project_library_asset(skin_key: str, asset_id: str, root: str | Path | None = None) -> dict[str, Any]:
    _project, _context, project_folder = _layer_studio_project_context(skin_key, root)
    return layer_studio.delete_project_library_asset(project_folder, asset_id)


def insert_layer_studio_project_library_asset(
    skin_key: str,
    asset_id: str,
    root: str | Path | None = None,
    placement: dict[str, Any] | None = None,
    surface_key: str | None = None,
    layer_document: dict[str, Any] | None = None,
) -> dict[str, Any]:
    project, context, project_folder = _layer_studio_project_context(skin_key, root, surface_key)
    result = layer_studio.insert_project_library_asset_layer(
        project_folder,
        asset_id,
        context.get("textureSize") if isinstance(context.get("textureSize"), dict) else {"width": layer_studio.TEXTURE_SIZE, "height": layer_studio.TEXTURE_SIZE},
        placement,
        context.get("activeSurfaceKey") or surface_key,
        layer_document,
    )
    return {
        **result,
        "skinKey": skin_key,
        "project": project,
        "layerStudio": layer_studio.workspace_context({"project": project}, get_livery_library_root(), result.get("surfaceKey") or surface_key),
    }


def insert_layer_studio_library_asset(
    skin_key: str,
    asset_id: str,
    root: str | Path | None = None,
    placement: dict[str, Any] | None = None,
    surface_key: str | None = None,
    layer_document: dict[str, Any] | None = None,
) -> dict[str, Any]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canUseEditablePreviewTools")
    prepared = prepare_livery_preview_project(skin_key, root)
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    project_folder = str(project.get("projectFolder") or "").strip()
    if not project_folder:
        raise ValueError("Layer Studio needs a LiveryProjects workspace folder")
    context = layer_studio.workspace_context({"project": project}, get_livery_library_root(), surface_key)
    result = layer_studio.insert_library_asset_layer(
        project_folder,
        get_livery_library_root(),
        asset_id,
        context.get("textureSize") if isinstance(context.get("textureSize"), dict) else {"width": layer_studio.TEXTURE_SIZE, "height": layer_studio.TEXTURE_SIZE},
        placement,
        context.get("activeSurfaceKey") or surface_key,
        layer_document,
    )
    return {
        **result,
        "skinKey": skin_key,
        "project": project,
        "layerStudio": layer_studio.workspace_context({"project": project}, get_livery_library_root(), result.get("surfaceKey") or surface_key),
    }


def save_layer_studio_project(
    skin_key: str,
    document: dict[str, Any],
    root: str | Path | None = None,
    surface_key: str | None = None,
) -> dict[str, Any]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canUseEditablePreviewTools")
    prepared = prepare_livery_preview_project(skin_key, root)
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    project_folder = str(project.get("projectFolder") or "").strip()
    if not project_folder:
        raise ValueError("Layer Studio needs a LiveryProjects workspace folder")
    result = layer_studio.save_project_document(project_folder, document, get_livery_library_root(), surface_key)
    return {
        **result,
        "skinKey": skin_key,
        "project": project,
        "layerStudio": layer_studio.workspace_context({"project": project}, get_livery_library_root(), result.get("surfaceKey") or surface_key),
    }


def bake_layer_studio_preview(
    skin_key: str,
    data_url: str,
    root: str | Path | None = None,
    surface_key: str | None = None,
) -> dict[str, Any]:
    entry, skins, index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canUseEditablePreviewTools")
    value = str(data_url or "").strip()
    prefix = "data:image/png;base64,"
    if not value.startswith(prefix):
        raise ValueError("Layer Studio bake expects a PNG data URL")
    try:
        raw = base64.b64decode(value[len(prefix):], validate=True)
    except Exception as exc:
        raise ValueError("Layer Studio bake PNG data could not be decoded") from exc
    try:
        from PIL import Image
        with Image.open(io.BytesIO(raw)) as image:
            if image.format != "PNG":
                raise ValueError("Layer Studio bake output is not a valid PNG")
            width, height = image.size
    except ValueError:
        raise
    except Exception as exc:
        raise ValueError(f"Layer Studio bake PNG could not be validated: {exc}") from exc
    prepared = prepare_livery_preview_project(skin_key, root)
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    project_folder_text = str(project.get("projectFolder") or prepared.get("projectFolder") or "").strip()
    if not project_folder_text:
        raise ValueError("Layer Studio needs a LiveryProjects workspace folder")
    project_folder = Path(project_folder_text).resolve()
    context = layer_studio.workspace_context({"project": project}, get_livery_library_root(), surface_key)
    active_surface_key = str(context.get("activeSurfaceKey") or layer_studio.DEFAULT_SURFACE_KEY)
    active_surface = next(
        (
            row
            for row in context.get("textureSurfaces", [])
            if isinstance(row, dict) and str(row.get("key") or "") == active_surface_key
        ),
        {},
    )
    active_surface_label = str(active_surface.get("label") or active_surface_key)
    if active_surface.get("bakeEnabled") is False:
        label = str(active_surface.get("label") or active_surface_key)
        raise ValueError(f"{label} has separate Layer Studio layers, but texture update is disabled until spec-part merging is supported.")
    expected_size_data = context.get("textureSize") if isinstance(context.get("textureSize"), dict) else {}
    expected_width = int(expected_size_data.get("width") or layer_studio.TEXTURE_SIZE)
    expected_height = int(expected_size_data.get("height") or layer_studio.TEXTURE_SIZE)
    if width != expected_width or height != expected_height:
        raise ValueError(f"Layer Studio bake output must be {expected_width}x{expected_height}, got {width}x{height}")

    active_texture = context.get("activeTexture") if isinstance(context.get("activeTexture"), dict) else {}
    target_text = str(active_texture.get("path") or "").strip()
    target = Path(target_text).resolve() if target_text else (project_folder / layer_studio.BAKED_TEXTURE_NAME).resolve()
    if not _is_path_within(target, project_folder):
        raise ValueError("Layer Studio bake target must stay inside the LiveryProjects workspace")
    target.parent.mkdir(parents=True, exist_ok=True)
    previous_target_bytes = target.read_bytes() if target.exists() else None
    base_info = context.get("baseTexture") if isinstance(context.get("baseTexture"), dict) else {}
    base_text = str(base_info.get("path") or "").strip()
    base_texture = Path(base_text).resolve() if base_text else (project_folder / layer_studio.PROJECT_DIRNAME / active_surface_key / layer_studio.BASE_TEXTURE_NAME).resolve()
    base_created = False
    base_migrated = False
    legacy_base_texture = (project_folder / layer_studio.LEGACY_BASE_TEXTURE_NAME).resolve()
    if active_surface_key == layer_studio.DEFAULT_SURFACE_KEY and not base_texture.exists() and legacy_base_texture.exists():
        base_texture.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(legacy_base_texture, base_texture)
        base_migrated = True
    if not base_texture.exists() and target.exists():
        base_texture.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(target, base_texture)
        base_created = True
    backup = safe_write_bytes(target, raw)
    if active_surface_key == "ext_banner_c":
        project["bannerTexturePath"] = str(target)
        project["liveBannerTexturePath"] = str(target)
        project["bannerTextureName"] = target.name
        project["liveBannerTextureName"] = target.name
    elif active_surface_key == "ext_skin_d":
        project["detailTexturePath"] = str(target)
        project["liveDetailTexturePath"] = str(target)
        project["detailTextureName"] = target.name
        project["liveDetailTextureName"] = target.name
    else:
        project["texturePath"] = str(target)
        project["liveTexturePath"] = str(target)
        project["textureName"] = target.name
        project["liveTextureName"] = target.name
    official_external_refresh: dict[str, Any] = {}
    official_manifest_update: dict[str, Any] = {}
    official_placeholder_state: dict[str, Any] = {}
    if (
        project_types.project_type_for_entry(entry) == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY
        and official_external_workflow.role_for_entry(entry) == official_external_workflow.ROLE_PROJECT
    ):
        official_placeholder_state = official_external_project_assets.mark_slot_as_user_authored(
            project_folder,
            active_surface_key,
        )
        official_external_refresh = official_external_project_assets.refresh_external_livery_json(
            project_folder,
            car_guid=str(entry.get("car_guid") or ""),
            material_batch=_official_external_entry_material_batch(entry),
        )
        official_manifest_update = _update_official_external_project_manifest(
            project_folder,
            official_external_refresh.get("assetStatus") if isinstance(official_external_refresh.get("assetStatus"), dict) else {},
        )
    changed = previous_target_bytes != raw
    game_update_required = False
    settings_path = ""
    if changed and bool(entry.get("active")):
        _mark_my_skin_game_sync_required(
            entry,
            f"Layer Studio {active_surface_label} updated",
            f"layer_studio_texture_{active_surface_key}",
        )
        skins[index] = entry
        settings_path = save_my_skins(skins)
        game_update_required = True
    return {
        "skinKey": skin_key,
        "path": str(target),
        "surfaceKey": active_surface_key,
        "surfaceLabel": active_surface_label,
        "backup": str(backup) if backup else "",
        "changed": changed,
        "width": width,
        "height": height,
        "baseTexturePath": str(base_texture),
        "baseTextureCreated": base_created,
        "baseTextureMigrated": base_migrated,
        "legacyBaseTexturePath": str(legacy_base_texture) if base_migrated else "",
        "gameUpdateRequired": game_update_required,
        "gameUpdateMessage": (
            f"{active_surface_label} changed. Update in Game is required."
            if game_update_required else (
                f"{active_surface_label} changed locally. Activate in Game to publish it."
                if changed else
                f"{active_surface_label} is already current."
            )
        ),
        "gameSync": entry.get("game_sync") or {},
        "item": entry,
        "settingsPath": settings_path,
        "officialExternalRefresh": official_external_refresh,
        "officialManifest": official_manifest_update,
        "officialPlaceholderState": official_placeholder_state,
        "project": project,
        "layerStudio": layer_studio.workspace_context({"project": project}, get_livery_library_root(), active_surface_key),
    }


def _find_texture_by_stem(folder: Path, stem: str) -> Path | None:
    if not folder.exists() or not folder.is_dir():
        return None
    target = stem.lower()
    for path in sorted(folder.rglob("*.texture"), key=lambda row: (len(row.relative_to(folder).parts), str(row.relative_to(folder)).lower())):
        if path.is_file() and path.suffix.lower() == ".texture" and path.stem.lower() == target:
            return path
    return None


EXT_SKIN_PROJECT_TEXTURES: dict[str, dict[str, str]] = {
    "ext_skin_c": {"label": "EXT_SKIN_C", "png": "EXT_SKIN_C.png", "pathKey": "liveTexturePath", "nameKey": "liveTextureName"},
    "ext_skin_d": {"label": "EXT_Skin_D", "png": "EXT_Skin_D.png", "pathKey": "liveDetailTexturePath", "nameKey": "liveDetailTextureName"},
    "ext_skin_d_2": {"label": "EXT_Skin_D_2", "png": "EXT_Skin_D_2.png", "pathKey": "liveDetail2TexturePath", "nameKey": "liveDetail2TextureName"},
    "ext_skin_drift_d": {"label": "EXT_Skin_Drift_D", "png": "EXT_Skin_Drift_D.png", "pathKey": "liveDriftDetailTexturePath", "nameKey": "liveDriftDetailTextureName"},
    "ext_skin_drift_ao": {"label": "EXT_Skin_Drift_AO", "png": "EXT_Skin_Drift_AO.png", "pathKey": "liveDriftAoTexturePath", "nameKey": "liveDriftAoTextureName"},
    "ext_skin_m": {"label": "EXT_Skin_M", "png": "EXT_Skin_M.png", "pathKey": "liveMetalnessTexturePath", "nameKey": "liveMetalnessTextureName"},
    "ext_skin_r": {"label": "EXT_Skin_R", "png": "EXT_Skin_R.png", "pathKey": "liveRoughnessTexturePath", "nameKey": "liveRoughnessTextureName"},
    "ext_skin_cc": {"label": "EXT_Skin_CC", "png": "EXT_Skin_CC.png", "pathKey": "liveClearCoatTexturePath", "nameKey": "liveClearCoatTextureName"},
    "ext_skin_cr": {"label": "EXT_Skin_CR", "png": "EXT_Skin_CR.png", "pathKey": "liveClearCoatRoughnessTexturePath", "nameKey": "liveClearCoatRoughnessTextureName"},
    "ext_skin_s": {"label": "EXT_Skin_S", "png": "EXT_Skin_S.png", "pathKey": "liveScuffTexturePath", "nameKey": "liveScuffTextureName"},
}
EXT_SKIN_TEXTURE_ORDER = {stem: index for index, stem in enumerate(EXT_SKIN_PROJECT_TEXTURES)}
EXT_SKIN_PRIMARY_STEMS = {"ext_skin_c", "ext_skin_d"}


def _ext_skin_project_texture_meta(stem: str) -> dict[str, str]:
    normalized = str(stem or "").strip().lower()
    if normalized in EXT_SKIN_PROJECT_TEXTURES:
        return dict(EXT_SKIN_PROJECT_TEXTURES[normalized])
    suffix = re.sub(r"[^0-9a-z]+", "_", normalized.removeprefix("ext_skin_")).strip("_") or "extra"
    label = f"EXT_Skin_{suffix.upper()}"
    camel = "".join(part.capitalize() for part in suffix.split("_") if part)
    return {
        "label": label,
        "png": f"{label}.png",
        "pathKey": f"live{camel}TexturePath",
        "nameKey": f"live{camel}TextureName",
    }


def _is_path_within(path: Path, root: Path) -> bool:
    try:
        path.resolve().relative_to(root.resolve())
        return True
    except Exception:
        return False


def _livery_project_rel(base: Path, path: Path) -> str:
    try:
        return str(path.resolve().relative_to(base.resolve())).replace("/", "\\")
    except Exception:
        return str(path)


def _resolve_project_texture_path(project: dict[str, Any], project_folder: Path, stem: str) -> Path:
    meta = _ext_skin_project_texture_meta(stem)
    raw = str(project.get(meta["pathKey"]) or "").strip()
    if raw:
        candidate = Path(raw)
        return candidate if candidate.is_absolute() else project_folder / candidate
    return project_folder / meta["png"]


def _material_ext_skin_texture_specs(
    game_root: Path,
    skin: dict[str, Any],
    project: dict[str, Any],
    skin_folder: Path | None,
    *,
    local_only: bool = False,
) -> list[dict[str, Any]]:
    if skin_folder is None or not skin_folder.exists():
        return []
    project_folder_text = str(project.get("projectFolder") or "").strip()
    if not project_folder_text:
        return []
    project_folder = Path(project_folder_text)
    design = _find_design_for_skin(game_root, skin, local_only=local_only)
    if not design or not design.exists():
        return []
    try:
        slot_materials = extract_design_slot_materials(design.read_bytes())
    except Exception:
        return []
    material_root = _entry_local_root(skin) if local_only else game_root
    if material_root is None:
        material_root = skin_folder.parent
    found: dict[str, dict[str, Any]] = {}

    def add_spec(resolved: Path, slot_name: str = "", material: Path | None = None) -> None:
        if not resolved.exists() or resolved.suffix.lower() != ".texture":
            return
        stem = resolved.stem.lower()
        if not stem.startswith("ext_skin"):
            return
        if not _is_path_within(resolved, skin_folder):
            return
        meta = _ext_skin_project_texture_meta(stem)
        suggested_format = ""
        mips = resolved.with_suffix(".texturemips")
        if mips.exists():
            try:
                desc = parse_texture_descriptor(resolved)
                suggested_format, _ = choose_auto_format(resolved, desc, mips.stat().st_size, slot_name)
            except Exception:
                suggested_format = ""
        entry = found.setdefault(stem, {
            "label": meta["label"],
            "stem": stem,
            "png": str(_resolve_project_texture_path(project, project_folder, stem)),
            "texture": str(resolved),
            "slot": slot_name,
            "material": str(material) if material else "",
            "suggestedFormat": suggested_format,
            "optional": stem not in EXT_SKIN_PRIMARY_STEMS,
            "pathKey": meta["pathKey"],
            "nameKey": meta["nameKey"],
            "projectName": meta["png"],
        })
        if suggested_format == "bc4" and entry.get("suggestedFormat") != "bc4":
            entry.update({"slot": slot_name, "material": str(material) if material else entry.get("material", ""), "suggestedFormat": suggested_format})

    for material_refs in slot_materials.values():
        for material_ref in material_refs:
            material = find_material_file(material_root, str(material_ref or ""))
            if material is None or not material.exists():
                continue
            try:
                inspected = inspect_material(material, root=material_root)
            except Exception:
                continue
            for slot in inspected.get("texture_slots", []):
                value = str(slot.get("value") or "")
                if ".texture" not in value.lower():
                    continue
                resolved_text = str(slot.get("resolved_path") or "")
                resolved = Path(resolved_text) if resolved_text else material_resolve_ref(material_root, value)
                if not resolved or not resolved.exists() or resolved.suffix.lower() != ".texture":
                    continue
                slot_name = str(slot.get("name") or "")
                add_spec(resolved, slot_name, material)
    material_map_slots_by_key = {
        texture_key: slot_name
        for slot_name, texture_key in paint_only_livery.SLOT_TO_TEXTURE_KEY.items()
        if texture_key in set(LIVERY_MATERIAL_FINISH_TEXTURE_KEYS.values())
    }
    for texture_key, slot_name in material_map_slots_by_key.items():
        stem = Path(paint_only_livery.TEXTURE_FILENAMES.get(texture_key, "")).stem.lower()
        if not stem or stem in found:
            continue
        fallback = _find_texture_by_stem(skin_folder, stem)
        if fallback and fallback.exists():
            add_spec(fallback, slot_name, None)
    return [
        found[stem]
        for stem in sorted(found, key=lambda item: (EXT_SKIN_TEXTURE_ORDER.get(item, 1000), item))
    ]


def _livery_project_texture_specs(
    game_root: Path,
    skin: dict[str, Any],
    project: dict[str, Any],
    skin_folder: Path | None,
    *,
    local_only: bool = False,
    include_banner: bool = True,
) -> list[dict[str, Any]]:
    specs = _material_ext_skin_texture_specs(game_root, skin, project, skin_folder, local_only=local_only)
    present = {str(spec.get("stem") or "").lower() for spec in specs}
    project_folder = _project_folder_from_livery_project(project)
    for stem in ("ext_skin_c", "ext_skin_d"):
        if stem in present or project_folder is None:
            continue
        meta = _ext_skin_project_texture_meta(stem)
        source_texture = _find_texture_by_stem(skin_folder, stem) if skin_folder else None
        specs.append({
            "label": meta["label"],
            "stem": stem,
            "png": str(_resolve_project_texture_path(project, project_folder, stem)),
            "texture": str(source_texture or ""),
            "optional": stem == "ext_skin_d" and source_texture is None,
            "pathKey": meta["pathKey"],
            "nameKey": meta["nameKey"],
            "projectName": meta["png"],
        })
    specs.sort(key=lambda item: (EXT_SKIN_TEXTURE_ORDER.get(str(item.get("stem") or "").lower(), 1000), str(item.get("stem") or "")))
    if include_banner:
        banner_png = ""
        if project_folder is not None:
            raw_banner = str(project.get("liveBannerTexturePath") or project.get("bannerTexturePath") or "").strip()
            if raw_banner:
                raw_path = Path(raw_banner)
                banner_png = str(raw_path if raw_path.is_absolute() else project_folder / raw_path)
            else:
                banner_png = str(project_folder / preview3d.LIVE_BANNER_TEXTURE_NAME)
        specs.append({
            "label": "EXT_Banner_C",
            "stem": "ext_banner_c",
            "png": banner_png,
            "texture": "",
            "banner": True,
            "optional": True,
        })
    return specs


def _sync_read_only_livery_preview_textures(
    game_root: Path,
    skin: dict[str, Any],
    project: dict[str, Any],
    skin_folder: Path | None,
    *,
    local_only: bool = False,
) -> dict[str, Any]:
    specs = _livery_project_texture_specs(game_root, skin, project, skin_folder, local_only=local_only, include_banner=True)
    allowed_stems = {"ext_skin_c", "ext_banner_c"}
    items: list[dict[str, Any]] = []
    for spec in specs:
        stem = str(spec.get("stem") or "").strip().lower()
        if stem not in allowed_stems:
            continue
        target_text = str(spec.get("png") or "").strip()
        if not target_text:
            continue
        target = Path(target_text)
        source = (
            _find_banner_texture_for_skin(
                game_root,
                skin,
                skin_folder,
                local_only=local_only,
                allow_spatmap_fallback=_entry_uses_advanced_banner_spatmap_fallback(skin),
            )
            if spec.get("banner")
            else Path(str(spec.get("texture") or "")) if str(spec.get("texture") or "").strip() else _find_texture_by_stem(skin_folder, stem) if skin_folder else None
        )
        if source is None or not source.exists():
            fallback = _preview_fallback_png_for_missing_texture(spec, target, overwrite=False)
            items.append({"label": spec.get("label") or stem, "status": fallback.get("status") or "missing", "source": "", "target": str(target), "reason": "source missing"})
            continue
        expected_size = _texture_descriptor_image_size(source)
        if not source.with_suffix(".texturemips").exists():
            fallback = _preview_fallback_png_for_missing_texture(spec, target, overwrite=False, size=expected_size)
            items.append({"label": spec.get("label") or stem, "status": fallback.get("status") or "missing", "source": str(source), "target": str(target), "reason": "texturemips missing"})
            continue
        size_mismatch = _project_png_texture_size_mismatch(target, source)
        overwrite = not target.exists() or source.stat().st_mtime > target.stat().st_mtime or size_mismatch
        if not overwrite:
            items.append({"label": spec.get("label") or stem, "status": "skipped", "source": str(source), "target": str(target), "reason": "target current"})
            continue
        try:
            converted = convert_texture_to_png(source, target, overwrite=True, create_dds=False)
            items.append({
                "label": spec.get("label") or stem,
                "status": "imported" if not converted.get("skipped") else "skipped",
                "source": str(source),
                "target": str(target),
                "reason": converted.get("reason") or "",
            })
        except Exception as exc:
            fallback = _preview_fallback_png_for_missing_texture(spec, target, overwrite=True, size=expected_size)
            items.append({
                "label": spec.get("label") or stem,
                "status": fallback.get("status") or "skipped",
                "source": str(source),
                "target": str(target),
                "reason": f"preview conversion skipped: {exc}",
                "warning": str(exc),
            })
    return {
        "items": items,
        "imported": sum(1 for item in items if item.get("status") == "imported"),
        "skipped": sum(1 for item in items if item.get("status") == "skipped"),
        "missing": sum(1 for item in items if str(item.get("reason") or "").endswith("missing") or item.get("status") == "missing"),
    }


def _update_livery_project_material_texture_metadata(result: dict[str, Any], specs: list[dict[str, Any]]) -> None:
    project = result.get("project") if isinstance(result.get("project"), dict) else {}
    project_path_text = str(result.get("projectPath") or project.get("projectPath") or "").strip()
    project_folder_text = str(result.get("projectFolder") or project.get("projectFolder") or "").strip()
    if not project_path_text or not project_folder_text:
        return
    project_path = Path(project_path_text)
    project_folder = Path(project_folder_text)
    material_maps: list[dict[str, Any]] = []
    for spec in specs:
        if spec.get("banner"):
            continue
        stem = str(spec.get("stem") or "").lower()
        if not stem.startswith("ext_skin"):
            continue
        meta = _ext_skin_project_texture_meta(stem)
        png = Path(str(spec.get("png") or _resolve_project_texture_path(project, project_folder, stem)))
        project[meta["nameKey"]] = meta["png"]
        project[meta["pathKey"]] = str(png)
        material_maps.append({
            "label": meta["label"],
            "stem": stem,
            "slot": spec.get("slot") or "",
            "suggestedFormat": spec.get("suggestedFormat") or "",
            "name": meta["png"],
            "path": str(png),
            "optional": bool(spec.get("optional")),
        })
    project["liveMaterialTextureMaps"] = material_maps
    result["liveMaterialTextureMaps"] = material_maps
    if not project_path.exists():
        return
    try:
        stored = json.loads(project_path.read_text(encoding="utf-8"))
        if not isinstance(stored, dict):
            return
        stored_maps: list[dict[str, Any]] = []
        for spec in specs:
            if spec.get("banner"):
                continue
            stem = str(spec.get("stem") or "").lower()
            if not stem.startswith("ext_skin"):
                continue
            meta = _ext_skin_project_texture_meta(stem)
            png = Path(str(spec.get("png") or _resolve_project_texture_path(project, project_folder, stem)))
            rel = _livery_project_rel(project_folder, png)
            stored[meta["nameKey"]] = meta["png"]
            stored[meta["pathKey"]] = rel
            stored_maps.append({
                "label": meta["label"],
                "stem": stem,
                "slot": spec.get("slot") or "",
                "suggestedFormat": spec.get("suggestedFormat") or "",
                "name": meta["png"],
                "path": rel,
                "optional": bool(spec.get("optional")),
            })
        stored["liveMaterialTextureMaps"] = stored_maps
        project_path.write_text(json.dumps(_json_ready(stored), indent=2, ensure_ascii=False), encoding="utf-8")
    except Exception:
        return


PLACEHOLDER_PNG_BYTES = base64.b64decode(
    "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhQGOSHzRgQAAAABJRU5ErkJggg=="
)


def _web_asset_path(*parts: str) -> Path | None:
    roots = [
        get_program_root() / "web" / "assets",
        get_program_root() / "_internal" / "web" / "assets",
        Path(__file__).resolve().parent / "web" / "assets",
    ]
    for root in roots:
        candidate = root.joinpath(*parts)
        if candidate.exists():
            return candidate
    return None


def ensure_preview_placeholder_png(target: str | Path, overwrite: bool = False) -> dict[str, Any]:
    out = Path(target)
    if out.exists() and not overwrite:
        return {"path": str(out), "status": "skipped", "source": "", "reason": "target exists"}
    out.parent.mkdir(parents=True, exist_ok=True)
    placeholder = _web_asset_path("placeholder.png")
    if placeholder and placeholder.exists():
        shutil.copyfile(placeholder, out)
        return {"path": str(out), "status": "placeholder", "source": str(placeholder), "reason": "source missing"}
    out.write_bytes(PLACEHOLDER_PNG_BYTES)
    return {"path": str(out), "status": "placeholder", "source": "embedded", "reason": "source missing"}


def ensure_preview_solid_png(
    target: str | Path,
    rgba: tuple[int, int, int, int] = (0, 0, 0, 255),
    size: int | tuple[int, int] = 4096,
    overwrite: bool = False,
) -> dict[str, Any]:
    out = Path(target)
    replace_placeholder = out.exists() and _is_preview_placeholder_png(out)
    if out.exists() and not overwrite and not replace_placeholder:
        return {"path": str(out), "status": "skipped", "source": "", "reason": "target exists"}
    out.parent.mkdir(parents=True, exist_ok=True)
    if replace_placeholder:
        backup_file(out)
    from PIL import Image
    if isinstance(size, tuple):
        width, height = size
    else:
        width = height = int(size)
    image = Image.new("RGBA", (max(1, int(width)), max(1, int(height))), tuple(int(value) for value in rgba))
    image.save(out)
    return {"path": str(out), "status": "solid", "source": "generated", "reason": "neutral fallback"}


def _png_image_size(path: str | Path) -> tuple[int, int] | None:
    candidate = Path(path)
    if not candidate.exists():
        return None
    try:
        from PIL import Image
        with Image.open(candidate) as image:
            return (int(image.width), int(image.height))
    except Exception:
        return None


def _texture_descriptor_image_size(texture_path: str | Path | None) -> tuple[int, int] | None:
    if texture_path is None:
        return None
    texture = Path(texture_path)
    if not texture.exists():
        return None
    try:
        desc = parse_texture_descriptor(texture)
        width = int(desc.get("width") or 0)
        height = int(desc.get("height") or 0)
    except Exception:
        return None
    if width <= 0 or height <= 0:
        return None
    return (width, height)


def _project_png_texture_size_mismatch(png_path: str | Path, texture_path: str | Path | None) -> bool:
    expected = _texture_descriptor_image_size(texture_path)
    actual = _png_image_size(png_path)
    return bool(expected and actual and actual != expected)


def _is_solid_black_png(path: str | Path) -> bool:
    candidate = Path(path)
    if not candidate.exists():
        return False
    try:
        from PIL import Image
        with Image.open(candidate) as image:
            normalized = image.convert("RGBA")
            extrema = normalized.getextrema()
    except Exception:
        return False
    if len(extrema) < 3:
        return False
    return all(channel == (0, 0) for channel in extrema[:3])


def _repair_neutral_project_png_size(
    png_path: str | Path,
    texture_path: str | Path,
    stem: str,
) -> dict[str, Any]:
    normalized_stem = str(stem or "").strip().lower()
    if normalized_stem not in {"ext_skin_d", "ext_skin_d_2", "ext_skin_drift_d"}:
        return {"repaired": False, "reason": "texture is not a neutral detail map"}
    expected_size = _texture_descriptor_image_size(texture_path)
    actual_size = _png_image_size(png_path)
    if not expected_size or not actual_size or actual_size == expected_size:
        return {"repaired": False, "reason": "size is already valid"}
    if not _is_solid_black_png(png_path):
        return {"repaired": False, "reason": "project PNG is not a neutral black map"}
    result = ensure_preview_solid_png(png_path, (0, 0, 0, 255), size=expected_size, overwrite=True)
    return {
        "repaired": True,
        "reason": "neutral detail map resized to texture descriptor",
        "path": result.get("path") or str(png_path),
        "previousSize": {"width": actual_size[0], "height": actual_size[1]},
        "expectedSize": {"width": expected_size[0], "height": expected_size[1]},
    }


def _preview_fallback_png_for_missing_texture(spec: dict[str, Any], target: Path, overwrite: bool = False, size: tuple[int, int] | None = None) -> dict[str, Any]:
    stem = str(spec.get("stem") or "").strip().lower()
    if stem == "ext_skin_c":
        return ensure_preview_solid_png(target, (255, 255, 255, 255), size=size or 4096, overwrite=overwrite)
    if stem in {"ext_skin_d", "ext_skin_d_2", "ext_skin_drift_d"}:
        return ensure_preview_solid_png(target, (0, 0, 0, 255), size=size or 4096, overwrite=overwrite)
    return ensure_preview_placeholder_png(target, overwrite=overwrite)


def _is_preview_placeholder_png(path: str | Path) -> bool:
    candidate = Path(path)
    if not candidate.exists() or not candidate.is_file():
        return False
    placeholder = _web_asset_path("placeholder.png")
    try:
        candidate_hash = hashlib.sha256(candidate.read_bytes()).hexdigest()
        if placeholder and placeholder.exists():
            return candidate_hash == hashlib.sha256(placeholder.read_bytes()).hexdigest()
        return candidate_hash == hashlib.sha256(PLACEHOLDER_PNG_BYTES).hexdigest()
    except Exception:
        return False


def _local_skin_asset_path(skin: dict[str, Any], rel_path: str | None) -> Path | None:
    rel_norm = normalize_skin_asset_rel_path(str(rel_path or ""))
    local_root = _entry_local_root(skin)
    if not local_root or not rel_norm:
        return None
    return local_root / Path(*split_content_path(rel_norm))


def _find_design_for_skin(game_root: Path, skin: dict[str, Any], local_only: bool = False) -> Path | None:
    files = dict(skin.get("files", {}) or {})
    design_rel = str(files.get("design") or "").strip()
    if design_rel:
        local_design = _local_skin_asset_path(skin, design_rel)
        if local_design and local_design.exists():
            return local_design
        if local_only:
            return None
        design = resolve_content_path(game_root, design_rel)
        if design.exists():
            return design
    if local_only:
        return None
    car_content_path = str(skin.get("car_content_path") or f"content\\cars\\{skin.get('car_model') or ''}").strip()
    skin_id = str(skin.get("skin_id") or "").strip()
    candidate = resolve_content_path(game_root, f"{car_content_path}\\skins\\{skin_id}.design")
    return candidate if candidate.exists() else None


def _find_banner_texture_for_skin(
    game_root: Path,
    skin: dict[str, Any],
    skin_folder: Path | None,
    local_only: bool = False,
    *,
    allow_spatmap_fallback: bool = False,
) -> Path | None:
    if skin_folder:
        local = _find_texture_by_stem(skin_folder, "ext_banner_c")
        if local:
            return local
    design = _find_design_for_skin(game_root, skin, local_only=local_only)
    if not design or not design.exists():
        return None
    try:
        slots = extract_design_slot_materials(design.read_bytes())
    except Exception:
        return None
    spatmap_fallbacks: list[Path] = []
    for material_rel in slots.get("EXT_BANNER", []):
        if local_only:
            local_root = _entry_local_root(skin)
            material = find_material_file(local_root, material_rel) if local_root else None
            if not material or not material.exists():
                material = _local_skin_asset_path(skin, material_rel)
            if not material or not material.exists():
                continue
        else:
            material = find_material_file(game_root, material_rel)
        if not material or not material.exists():
            continue
        try:
            local_root = _entry_local_root(skin)
            material_info = inspect_material(material, root=local_root if local_only and local_root else game_root)
        except Exception:
            continue
        for slot in material_info.get("texture_slots", []):
            slot_name = str(slot.get("name") or "")
            value = str(slot.get("value") or "")
            if ".texture" not in value.lower():
                continue
            if local_only:
                local_root = _entry_local_root(skin)
                resolved = material_resolve_ref(local_root, value) if local_root else None
            else:
                resolved = material_resolve_ref(game_root, value) or resolve_content_path(game_root, value)
            if resolved and resolved.exists():
                if "ext_banner_c.texture" in value.lower() or resolved.stem.lower() == "ext_banner_c":
                    return resolved
                if (
                    allow_spatmap_fallback
                    and slot_name == "SpatMap"
                    and "default_material" not in value.lower()
                    and "banner" in resolved.name.lower()
                ):
                    spatmap_fallbacks.append(resolved)
    car_content_path = str(skin.get("car_content_path") or f"content\\cars\\{skin.get('car_model') or ''}").strip()
    if car_content_path:
        fallback = resolve_content_path(game_root, f"{car_content_path}\\textures\\ext_banner_c.texture")
        if fallback.exists():
            return fallback
    if allow_spatmap_fallback and spatmap_fallbacks:
        return spatmap_fallbacks[0]
    source_skin = skin.get("source_skin") if isinstance(skin.get("source_skin"), dict) else {}
    source_design = str(source_skin.get("design") or "").strip()
    source_skin_folder_rel = str(source_skin.get("skin_folder") or "").strip()
    if allow_spatmap_fallback and local_only and (source_design or source_skin_folder_rel):
        source_entry = {
            **skin,
            "files": {
                "design": source_design,
                "skin_folder": source_skin_folder_rel,
            },
            "source_skin": {},
            "car_content_path": source_skin.get("car_content_path") or skin.get("car_content_path") or car_content_path,
        }
        source_skin_folder = _disk_from_game_rel(game_root, source_skin_folder_rel) if source_skin_folder_rel else None
        source_texture = _find_banner_texture_for_skin(
            game_root,
            source_entry,
            source_skin_folder,
            local_only=False,
            allow_spatmap_fallback=True,
        )
        if source_texture is not None:
            return source_texture
    return None


def _find_ao_texture_for_skin(game_root: Path, skin: dict[str, Any], skin_folder: Path | None, local_only: bool = False) -> Path | None:
    if skin_folder:
        local = _find_texture_by_stem(skin_folder, "ext_skin_ao")
        if local:
            return local
    design = _find_design_for_skin(game_root, skin, local_only=local_only)
    if design and design.exists():
        try:
            slot_materials = extract_design_slot_materials(design.read_bytes())
        except Exception:
            slot_materials = {}
        material_root = _entry_local_root(skin) if local_only else game_root
        if material_root is None:
            material_root = skin_folder.parent if skin_folder else game_root
        for material_refs in slot_materials.values():
            for material_ref in material_refs:
                material = find_material_file(material_root, str(material_ref or ""))
                if not material or not material.exists():
                    continue
                try:
                    material_info = inspect_material(material, root=material_root)
                except Exception:
                    continue
                for slot in material_info.get("texture_slots", []):
                    slot_name = str(slot.get("name") or "")
                    if _preview_material_channel(slot_name) != "ao":
                        continue
                    value = str(slot.get("value") or "")
                    if ".texture" not in value.lower():
                        continue
                    candidates: list[Path] = []
                    resolved_text = str(slot.get("resolved_path") or "").strip()
                    if resolved_text:
                        candidates.append(Path(resolved_text))
                    candidates.extend(
                        path
                        for path in (
                            material_resolve_ref(material_root, value),
                            resolve_content_path(game_root, value),
                            material_resolve_ref(game_root, value),
                        )
                        if path is not None
                    )
                    for candidate in candidates:
                        if candidate.exists() and candidate.suffix.lower() == ".texture":
                            return candidate
    car_content_path = str(skin.get("car_content_path") or f"content\\cars\\{skin.get('car_model') or ''}").strip()
    if car_content_path:
        fallback = resolve_content_path(game_root, f"{car_content_path}\\textures\\ext_skin_ao.texture")
        if fallback.exists():
            return fallback
    return None


def convert_texture_to_png(
    texture_path: str | Path,
    png_path: str | Path,
    overwrite: bool = False,
    create_dds: bool = False,
    format_name: str = "auto",
) -> dict[str, Any]:
    texture = Path(texture_path)
    out = Path(png_path)
    dds_out = out.with_suffix(".dds")
    mips = texture.with_suffix(".texturemips")
    if not texture.exists():
        raise FileNotFoundError(f"Texture file not found: {texture}")
    if not mips.exists():
        raise FileNotFoundError(f"Texturemips file not found: {mips}")
    if out.exists() and not overwrite and (not create_dds or dds_out.exists()):
        return {"path": str(out), "ddsPath": str(dds_out) if dds_out.exists() else "", "skipped": True, "reason": "exists"}

    texconv = find_texconv_executable()
    if texconv is None:
        raise RuntimeError("texconv.exe was not found. Place it in LiveryLab/tools or configure it in Settings.")

    out.parent.mkdir(parents=True, exist_ok=True)
    tmp_dir = create_short_work_temp_dir("pv")
    try:
        dds_path = tmp_dir / "x.dds"
        convert_to_dds(texture, mips, dds_path, format_name)
        dds_status = "skipped"
        if create_dds:
            if dds_out.exists() and not overwrite:
                dds_status = "skipped"
            else:
                if dds_out.exists() and overwrite:
                    backup_file(dds_out)
                shutil.copyfile(dds_path, dds_out)
                dds_status = "imported"
        process = run_cli_hidden(
            [str(texconv), "-nologo", "-y", "-f", "R8G8B8A8_UNORM", "-ft", "png", "-o", str(tmp_dir), str(dds_path)],
            capture_output=True,
            text=True,
            diagnostic_operation="texconv_texture_to_png",
            diagnostic_context={"input": dds_path, "output": out, "format": "R8G8B8A8_UNORM"},
        )
        if process.returncode != 0:
            details = "\n".join(part.strip() for part in [process.stdout, process.stderr] if str(part or "").strip())
            raise RuntimeError(f"texconv failed while exporting PNG for {texture.name}\n{details}".strip())
        generated = tmp_dir / f"{dds_path.stem}.png"
        if not generated.exists():
            matches = sorted(tmp_dir.glob("*.png"))
            generated = matches[0] if matches else generated
        if not generated.exists():
            raise RuntimeError(f"texconv did not produce a PNG output for {texture.name}")
        png_status = "skipped"
        if out.exists() and not overwrite:
            png_status = "skipped"
        else:
            if out.exists() and overwrite:
                backup_file(out)
            shutil.copyfile(generated, out)
            png_status = "imported"
    finally:
        cleanup_short_work_temp_dir(tmp_dir)
    return {
        "path": str(out),
        "ddsPath": str(dds_out) if dds_out.exists() else "",
        "skipped": png_status != "imported",
        "reason": png_status,
        "ddsStatus": dds_status,
        "format": str(format_name or "auto"),
    }


def import_livery_preview_textures(skin_key: str, root: str | Path | None = None, overwrite: bool = False, create_dds: bool = False) -> dict[str, Any]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canUseEditablePreviewTools")
    prepared = prepare_livery_preview_project(skin_key, root)
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    game_root = normalize_game_root(root or project.get("gameRoot") or DEFAULT_ROOT)
    key = str(skin_key or "").strip().lower()
    skin = next((row for row in list_my_skins(game_root) if str(row.get("key", "")).lower() == key), None)
    if not skin:
        raise KeyError(f"My Skin not found: {skin_key}")
    files = dict(skin.get("files", {}) or {})
    skin_folder = _local_skin_asset_path(skin, str(files.get("skin_folder") or ""))
    if not skin_folder or not skin_folder.exists():
        skin_folder = None
    local_root = _entry_local_root(skin)
    local_only = bool(skin_folder and local_root and _is_path_within(skin_folder, local_root))
    local_material_map_sync: dict[str, Any] = {}
    if local_only:
        local_material_map_sync = _ensure_livery_material_maps_in_local_skin(skin, game_root)
        skin_folder = _local_skin_asset_path(skin, str(files.get("skin_folder") or "")) or skin_folder

    specs = _livery_project_texture_specs(game_root, skin, project, skin_folder, local_only=local_only, include_banner=True)
    entry_start_point = skin.get("livery_start_point") if isinstance(skin.get("livery_start_point"), dict) else {}
    entry_start_mode = str(entry_start_point.get("mode") or entry_start_point.get("id") or "").strip().lower()
    blank_start_point = entry_start_mode == livery_start_points.BLANK_START_POINT_ID
    results: list[dict[str, Any]] = []
    for spec in specs:
        target = Path(str(spec.get("target") or spec.get("png") or ""))
        source = (
            _find_banner_texture_for_skin(
                game_root,
                skin,
                skin_folder,
                local_only=local_only,
                allow_spatmap_fallback=_entry_uses_advanced_banner_spatmap_fallback(skin),
            )
            if spec.get("banner")
            else Path(str(spec.get("texture") or "")) if str(spec.get("texture") or "").strip() else _find_texture_by_stem(skin_folder, str(spec["stem"])) if skin_folder else None
        )
        stem = str(spec.get("stem") or "").strip().lower()
        if stem == "ext_skin_c" and blank_start_point:
            expected_size = _texture_descriptor_image_size(source) if source is not None else None
            neutral = ensure_preview_solid_png(
                target,
                (255, 255, 255, 255),
                size=expected_size or 4096,
                overwrite=overwrite,
            )
            results.append({
                "label": spec["label"],
                "status": "neutralized" if neutral.get("status") == "solid" else neutral.get("status") or "skipped",
                "source": "generated",
                "target": str(target),
                "reason": "blank livery neutral base color" if neutral.get("status") == "solid" else neutral.get("reason") or "target exists",
            })
            continue
        if stem == "ext_skin_d" and blank_start_point:
            expected_size = _texture_descriptor_image_size(source) if source is not None else None
            neutral = ensure_preview_solid_png(
                target,
                (0, 0, 0, 255),
                size=expected_size or 4096,
                overwrite=True,
            )
            results.append({
                "label": spec["label"],
                "status": "neutralized",
                "source": "generated",
                "target": str(target),
                "reason": "blank livery neutral detail map",
            })
            continue
        preserve_original_detail = bool(
            stem == "ext_skin_d"
            and (_project_uses_original_livery_start_point(project) or _entry_has_original_livery_source(entry))
            and _project_body_base_color_enabled(project)
        )
        if preserve_original_detail:
            original_source = _original_livery_start_point_texture_source(game_root, entry, stem)
            if original_source is not None:
                source = original_source
                local_sync = _sync_original_start_point_texturemips_to_local_skin(entry, stem, original_source)
                if local_sync:
                    spec["localStartPointSync"] = local_sync
        if source is None:
            if spec.get("banner"):
                if target.exists() and _is_preview_placeholder_png(target):
                    backup_file(target)
                    target.unlink(missing_ok=True)
                results.append({"label": spec["label"], "status": "missing", "source": "", "target": str(target), "reason": "banner source missing"})
                continue
            if spec.get("optional"):
                results.append({"label": spec["label"], "status": "skipped", "source": "", "target": str(target), "reason": "optional local source missing"})
                continue
            placeholder = _preview_fallback_png_for_missing_texture(spec, target, overwrite=overwrite)
            results.append({"label": spec["label"], "status": placeholder["status"], "source": placeholder.get("source", ""), "target": str(target), "reason": "local source missing"})
            continue
        if not source.with_suffix(".texturemips").exists():
            expected_size = _texture_descriptor_image_size(source)
            if spec.get("banner"):
                if target.exists() and _is_preview_placeholder_png(target):
                    backup_file(target)
                    target.unlink(missing_ok=True)
                results.append({"label": spec["label"], "status": "missing", "source": str(source), "target": str(target), "reason": "banner texturemips missing"})
                continue
            if spec.get("optional"):
                results.append({"label": spec["label"], "status": "skipped", "source": str(source), "target": str(target), "reason": "optional local texturemips missing"})
                continue
            placeholder = _preview_fallback_png_for_missing_texture(spec, target, overwrite=overwrite, size=expected_size)
            results.append({"label": spec["label"], "status": placeholder["status"], "source": str(source), "target": str(target), "reason": "local texturemips missing"})
            continue
        dds_target = target.with_suffix(".dds")
        expected_size = _texture_descriptor_image_size(source)
        size_mismatch = _project_png_texture_size_mismatch(target, source)
        replace_neutral_detail_placeholder = bool(
            preserve_original_detail
            and target.exists()
            and not overwrite
            and _is_solid_black_png(target)
        )
        effective_overwrite = bool(overwrite or size_mismatch or replace_neutral_detail_placeholder or (spec.get("banner") and _is_preview_placeholder_png(target)))
        if target.exists() and not effective_overwrite and (not create_dds or dds_target.exists()):
            results.append({"label": spec["label"], "status": "skipped", "source": str(source), "target": str(target), "ddsTarget": str(dds_target) if dds_target.exists() else "", "reason": "target exists"})
            continue
        if stem == "ext_skin_d" and not preserve_original_detail:
            neutral = ensure_preview_solid_png(target, (0, 0, 0, 255), size=expected_size or 4096, overwrite=True)
            results.append({
                "label": spec["label"],
                "status": "neutralized",
                "source": str(source),
                "target": str(target),
                "ddsTarget": "",
                "ddsStatus": "",
                "reason": neutral.get("reason") or "black neutral material mask",
            })
            continue
        try:
            converted = convert_texture_to_png(source, target, overwrite=effective_overwrite, create_dds=create_dds)
            result_item = {
                "label": spec["label"],
                "status": "imported" if not converted.get("skipped") else "skipped",
                "source": str(source),
                "target": str(target),
                "reason": converted.get("reason") or "",
                "ddsTarget": converted.get("ddsPath") or "",
                "ddsStatus": converted.get("ddsStatus") or "",
            }
            if spec.get("localStartPointSync"):
                result_item["localStartPointSync"] = spec.get("localStartPointSync")
            results.append(result_item)
        except Exception as exc:
            if spec.get("banner") or spec.get("optional"):
                status = "skipped"
                reason = f"optional preview conversion skipped: {exc}"
            else:
                fallback = _preview_fallback_png_for_missing_texture(spec, target, overwrite=effective_overwrite, size=expected_size)
                status = fallback.get("status") or "skipped"
                reason = f"preview fallback generated after conversion error: {exc}"
            results.append({
                "label": spec["label"],
                "status": status,
                "source": str(source),
                "target": str(target),
                "reason": reason,
                "warning": str(exc),
                "ddsTarget": "",
                "ddsStatus": "skipped" if create_dds else "",
            })

    project_folder = Path(str(project.get("projectFolder") or prepared.get("projectFolder") or ""))
    project_path_text = str(project.get("projectPath") or prepared.get("projectPath") or "").strip()
    project_path = Path(project_path_text) if project_path_text else None
    ao_target_text = str(project.get("liveAoTexturePath") or "").strip()
    ao_target = Path(ao_target_text) if ao_target_text else project_folder / preview3d.LIVE_AO_TEXTURE_NAME
    if project_folder and ao_target_text and not ao_target.is_absolute():
        ao_target = project_folder / ao_target
    if project_folder:
        ao_source = _find_ao_texture_for_skin(game_root, skin, skin_folder, local_only=local_only)
        if ao_source is None:
            results.append({"label": "ext_skin_ao", "status": "skipped", "source": "", "target": str(ao_target), "reason": "optional AO source missing"})
        elif not ao_source.with_suffix(".texturemips").exists():
            results.append({"label": "ext_skin_ao", "status": "skipped", "source": str(ao_source), "target": str(ao_target), "reason": "optional AO texturemips missing"})
        else:
            if ao_target.exists() and not overwrite:
                results.append({"label": "ext_skin_ao", "status": "skipped", "source": str(ao_source), "target": str(ao_target), "reason": "target exists"})
            else:
                try:
                    converted_ao = convert_texture_to_png(ao_source, ao_target, overwrite=overwrite, create_dds=False)
                    results.append({
                        "label": "ext_skin_ao",
                        "status": "imported" if not converted_ao.get("skipped") else "skipped",
                        "source": str(ao_source),
                        "target": str(ao_target),
                        "reason": converted_ao.get("reason") or "",
                    })
                except Exception as exc:
                    results.append({
                        "label": "ext_skin_ao",
                        "status": "skipped",
                        "source": str(ao_source),
                        "target": str(ao_target),
                        "reason": f"optional preview conversion skipped: {exc}",
                        "warning": str(exc),
                    })
        if ao_target.exists():
            project["liveAoTexturePath"] = str(ao_target)
            project["aoTexturePath"] = str(ao_target)
            prepared["aoTexture"] = {"path": str(ao_target), "exists": True}
            if project_path and project_path.exists() and project_folder:
                try:
                    stored = json.loads(project_path.read_text(encoding="utf-8"))
                    if isinstance(stored, dict):
                        stored["liveAoTexturePath"] = _livery_project_rel(project_folder, ao_target)
                        stored["aoTexturePath"] = _livery_project_rel(project_folder, ao_target)
                        project_path.write_text(json.dumps(_json_ready(stored), indent=2, ensure_ascii=False), encoding="utf-8")
                except Exception:
                    pass

    return {
        **prepared,
        "imported": sum(1 for item in results if item.get("status") == "imported"),
        "skipped": sum(1 for item in results if item.get("status") == "skipped"),
        "ddsImported": sum(1 for item in results if item.get("ddsStatus") == "imported"),
        "missing": sum(1 for item in results if str(item.get("status")).startswith("missing")),
        "items": results,
        "materialMapLocalSync": local_material_map_sync,
    }


def _resolve_livery_start_point_texture(root: Path, value: str | Path | None) -> Path | None:
    text = str(value or "").strip()
    if not text:
        return None
    path = Path(text)
    if path.is_absolute():
        return path
    return root / Path(*split_content_path(text))


def _official_start_point_placeholder_can_replace(project_folder: Path, slot_id: str, target: Path) -> bool:
    if str(slot_id or "").strip().lower() == "ext_skin_c" and target.exists() and target.is_file():
        try:
            from PIL import Image
            with Image.open(target) as image:
                extrema = image.convert("RGBA").getextrema()
            if len(extrema) >= 4 and extrema[0] == (255, 255) and extrema[1] == (255, 255) and extrema[2] == (255, 255):
                return True
        except Exception:
            pass
    state_path = project_folder / official_external_project_assets.PLACEHOLDER_STATE
    if not state_path.exists() or not state_path.is_file() or not target.exists() or not target.is_file():
        return False
    try:
        state = json.loads(state_path.read_text(encoding="utf-8"))
    except Exception:
        return False
    slots = state.get("slots") if isinstance(state, dict) else {}
    row = slots.get(slot_id) if isinstance(slots, dict) else {}
    if not isinstance(row, dict):
        return False
    expected = str(row.get("sha256") or "").strip().lower()
    if not expected:
        return False
    try:
        hasher = hashlib.sha256()
        with target.open("rb") as stream:
            for chunk in iter(lambda: stream.read(1024 * 1024), b""):
                hasher.update(chunk)
        return hasher.hexdigest().lower() == expected
    except Exception:
        return False


def _official_start_point_template(entry: dict[str, Any], start_point: dict[str, Any]) -> dict[str, Any]:
    details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
    variant = details.get("vehicleVariant") if isinstance(details.get("vehicleVariant"), dict) else {}
    metadata = details.get("metadata") if isinstance(details.get("metadata"), dict) else {}
    metadata_variant = metadata.get("vehicleVariant") if isinstance(metadata.get("vehicleVariant"), dict) else {}
    template: dict[str, Any] = {}
    for source in (variant, metadata_variant, start_point):
        if not isinstance(source, dict):
            continue
        for key in ("materialPath", "material", "sourceMaterialPath", "sourceMaterial", "cardesign", "design", "designPath", "visualPreset", "mechanicalPreset", "carFinalState"):
            value = source.get(key)
            if value and not template.get(key):
                template[key] = value
    return template


def _apply_official_livery_start_point_to_project(
    entry: dict[str, Any],
    skins: list[dict[str, Any]],
    index: int,
    skin_key: str,
    root: str | Path | None,
    start_point: dict[str, Any],
    overwrite: bool = False,
    create_dds: bool = False,
) -> dict[str, Any]:
    project_folder = _livery_project_folder_for_entry(entry)
    if not project_folder.exists() or not project_folder.is_dir():
        raise FileNotFoundError(f"Official external project folder not found: {project_folder}")
    game_root = normalize_game_root(root or entry.get("game_root") or get_default_root_folder())
    car_id = str(entry.get("car_model") or start_point.get("carId") or "").strip()
    if not car_id:
        raise RuntimeError("Car id is not available for official start point import")

    inspected = dict(start_point)
    textures = inspected.get("textures") if isinstance(inspected.get("textures"), list) else []
    if not textures:
        inspected = livery_start_points.inspect_original_game_livery(
            root=game_root,
            car_id=car_id,
            workflow="official",
            template=_official_start_point_template(entry, start_point),
        )
        textures = inspected.get("textures") if isinstance(inspected.get("textures"), list) else []

    slot_by_id = {
        str(slot.get("id") or "").strip().lower(): slot
        for slot in official_external_project_assets.slot_definitions_for_material_batch(_official_external_entry_material_batch(entry))
        if str(slot.get("id") or "").strip().lower().startswith("ext_skin")
    }
    results: list[dict[str, Any]] = []
    for row in textures:
        if not isinstance(row, dict):
            continue
        stem = str(row.get("slotId") or "").strip().lower()
        slot = slot_by_id.get(stem)
        if not slot:
            continue
        source = _resolve_livery_start_point_texture(game_root, row.get("sourceTexturePath"))
        target = project_folder / str(slot.get("filename") or f"{stem}.png")
        item = {
            "label": slot.get("label") or row.get("label") or stem,
            "stem": stem,
            "source": str(source or ""),
            "target": str(target),
            "slot": row.get("materialSlot") or "",
            "material": row.get("materialPath") or "",
            "projectName": target.name,
        }
        if source is None or not source.exists():
            results.append({**item, "status": "missing", "reason": "source missing"})
            continue
        if not source.with_suffix(".texturemips").exists():
            results.append({**item, "status": "missing", "reason": "texturemips missing"})
            continue
        replace_placeholder = _official_start_point_placeholder_can_replace(project_folder, stem, target)
        if target.exists() and not overwrite and not replace_placeholder and (not create_dds or target.with_suffix(".dds").exists()):
            results.append({
                **item,
                "status": "skipped",
                "reason": "target exists",
                "ddsTarget": str(target.with_suffix(".dds")) if target.with_suffix(".dds").exists() else "",
            })
            continue
        try:
            converted = convert_texture_to_png(source, target, overwrite=overwrite or replace_placeholder, create_dds=create_dds)
            results.append({
                **item,
                "status": "imported" if not converted.get("skipped") else "skipped",
                "reason": converted.get("reason") or "",
                "ddsTarget": converted.get("ddsPath") or "",
                "ddsStatus": converted.get("ddsStatus") or "",
                "replacedGeneratedPlaceholder": replace_placeholder,
            })
        except Exception as exc:
            results.append({**item, "status": "error", "reason": str(exc), "warning": str(exc)})

    guid = _official_external_project_car_guid(entry, project_folder)
    refresh = official_external_project_assets.refresh_external_livery_json(
        project_folder,
        car_guid=guid,
        material_batch=_official_external_entry_material_batch(entry),
    )
    manifest_update = _update_official_external_project_manifest(
        project_folder,
        refresh.get("assetStatus") if isinstance(refresh.get("assetStatus"), dict) else {},
    )

    imported = sum(1 for item in results if item.get("status") == "imported")
    skipped = sum(1 for item in results if item.get("status") == "skipped")
    missing = sum(1 for item in results if item.get("status") == "missing")
    errors = [item for item in results if item.get("status") == "error"]
    details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
    details["liveryStartPoint"] = {
        "mode": livery_start_points.ORIGINAL_GAME_LIVERY_ID,
        "label": inspected.get("label") or "Original AC EVO livery",
        "source": inspected.get("source") or "game_loose_content",
        "materialPath": inspected.get("materialPath") or start_point.get("materialPath") or "",
        "appliedAt": datetime.now().isoformat(timespec="seconds"),
    }
    entry["official_external"] = details
    files = entry.get("files") if isinstance(entry.get("files"), dict) else {}
    primary = project_folder / "EXT_Skin_C.png"
    if primary.exists() and primary.is_file():
        files["primary_texture"] = primary.name
    entry["files"] = files
    skins[index] = entry
    saved = save_my_skins(skins)

    if errors and not imported:
        raise RuntimeError(str(errors[0].get("reason") or "official start point import failed"))
    return _json_ready({
        "skinKey": skin_key,
        "projectFolder": str(project_folder),
        "mode": livery_start_points.ORIGINAL_GAME_LIVERY_ID,
        "applied": bool(imported or skipped),
        "startPoint": inspected,
        "imported": imported,
        "skipped": skipped,
        "missing": missing,
        "errorCount": len(errors),
        "ddsImported": sum(1 for item in results if item.get("ddsStatus") == "imported"),
        "items": results,
        "warnings": list(dict.fromkeys([
            *[str(row) for row in inspected.get("warnings", []) or []],
            *[str(item.get("warning") or item.get("reason") or "") for item in errors if str(item.get("warning") or item.get("reason") or "").strip()],
        ])),
        "refresh": refresh,
        **manifest_update,
        "registryPath": str(saved),
    })


def _project_uses_original_livery_start_point(project: dict[str, Any]) -> bool:
    start_point = project.get("liveryStartPoint") if isinstance(project.get("liveryStartPoint"), dict) else {}
    mode = str(start_point.get("mode") or start_point.get("id") or "").strip().lower()
    return mode == livery_start_points.ORIGINAL_GAME_LIVERY_ID


def _project_body_base_color_enabled(project: dict[str, Any]) -> bool:
    if "bodyBaseColorEnabled" in project:
        return bool(project.get("bodyBaseColorEnabled"))
    capability = project.get("bodyBaseColorCapability") if isinstance(project.get("bodyBaseColorCapability"), dict) else {}
    return bool(capability.get("enabled"))


def _entry_has_original_livery_source(entry: dict[str, Any]) -> bool:
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    return bool(str(source_skin.get("material") or "").strip())


def _original_livery_start_point_texture_source(
    game_root: Path,
    entry: dict[str, Any],
    stem: str,
) -> Path | None:
    normalized_stem = str(stem or "").strip().lower()
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    source_material = norm_slashes(str(source_skin.get("material") or ""))
    car_id = str(entry.get("car_model") or "").strip()
    if not source_material or not car_id:
        return None
    source_template = {
        "material": source_material,
        "sourceMaterial": source_material,
        "cardesign": source_skin.get("cardesign") or "",
        "design": source_skin.get("design") or "",
        "visualPreset": source_skin.get("visual_preset") or "",
        "mechanicalPreset": source_skin.get("mechanical_preset") or "",
        "carFinalState": source_skin.get("carfinalstate") or "",
    }
    try:
        inspected = livery_start_points.inspect_original_game_livery(
            root=game_root,
            car_id=car_id,
            workflow="advanced",
            template=source_template,
        )
    except Exception:
        return None
    textures = inspected.get("textures") if isinstance(inspected.get("textures"), list) else []
    for row in textures:
        if not isinstance(row, dict):
            continue
        if str(row.get("slotId") or "").strip().lower() != normalized_stem:
            continue
        source = _resolve_livery_start_point_texture(game_root, row.get("sourceTexturePath"))
        if source and source.exists() and source.with_suffix(".texturemips").exists():
            return source
    return None


def _sync_original_start_point_texturemips_to_local_skin(
    entry: dict[str, Any],
    stem: str,
    source: Path | None,
) -> dict[str, Any]:
    normalized_stem = str(stem or "").strip().lower()
    if normalized_stem != "ext_skin_d" or source is None or not source.exists():
        return {}
    source_mips = source.with_suffix(".texturemips")
    if not source_mips.exists():
        return {"status": "missing", "source": str(source), "reason": "source texturemips missing"}
    files = entry.get("files") if isinstance(entry.get("files"), dict) else {}
    skin_folder = _local_skin_asset_path(entry, str(files.get("skin_folder") or ""))
    local_root = _entry_local_root(entry)
    if skin_folder is None or not skin_folder.exists() or local_root is None or not _is_path_within(skin_folder, local_root):
        return {}
    target = _find_texture_by_stem(skin_folder, normalized_stem)
    if target is None:
        return {"status": "missing", "source": str(source), "reason": "local texture missing"}
    target_mips = target.with_suffix(".texturemips")
    try:
        if target_mips.exists() and target_mips.read_bytes() == source_mips.read_bytes():
            return {
                "status": "current",
                "source": str(source),
                "target": str(target),
                "texturemips": str(target_mips),
            }
        target_mips.parent.mkdir(parents=True, exist_ok=True)
        shutil.copyfile(source_mips, target_mips)
        return {
            "status": "updated",
            "source": str(source),
            "target": str(target),
            "texturemips": str(target_mips),
            "reason": "restored original start point splat map",
        }
    except Exception as exc:
        return {"status": "error", "source": str(source), "target": str(target), "reason": str(exc)}


def _sync_livery_start_point_layer_studio_base(project_folder: Path, stem: str, texture_path: Path) -> dict[str, Any]:
    if stem not in {"ext_skin_c", "ext_skin_d"} or not texture_path.exists():
        return {}
    base_path = project_folder / layer_studio.PROJECT_DIRNAME / stem / layer_studio.SURFACE_BASE_TEXTURE_NAME
    layers_path = project_folder / layer_studio.PROJECT_DIRNAME / stem / layer_studio.LAYERS_FILE_NAME
    base_color_hidden = False
    try:
        if base_path.exists() and not _is_solid_black_png(base_path):
            status = "skipped"
            reason = "base exists"
        else:
            base_path.parent.mkdir(parents=True, exist_ok=True)
            shutil.copyfile(texture_path, base_path)
            status = "updated"
            reason = "initialized from start point texture"
        if stem == "ext_skin_c" or not _is_solid_black_png(texture_path):
            try:
                with Image.open(texture_path) as image:
                    texture_size = {"width": int(image.width), "height": int(image.height)}
            except Exception:
                texture_size = {"width": layer_studio.TEXTURE_SIZE, "height": layer_studio.TEXTURE_SIZE}
            document, _warnings = layer_studio.load_layer_document(
                layers_path,
                texture_size,
                stem,
                layer_studio.SURFACE_BASE_TEXTURE_NAME,
            )
            base_color = document.get("baseColor") if isinstance(document.get("baseColor"), dict) else {}
            base_color["visible"] = False
            base_color["autoHiddenForOriginalBaseTexture"] = True
            document["baseColor"] = base_color
            layer_studio.save_layer_document(
                layers_path,
                document,
                texture_size,
                stem,
                layer_studio.SURFACE_BASE_TEXTURE_NAME,
            )
            base_color_hidden = True
        return {
            "status": status,
            "path": str(base_path),
            "reason": reason,
            "baseColorHidden": base_color_hidden,
        }
    except Exception as exc:
        return {"status": "error", "path": str(base_path), "reason": str(exc)}


def apply_livery_start_point_to_project(
    skin_key: str,
    root: str | Path | None = None,
    start_point: dict[str, Any] | None = None,
    overwrite: bool = False,
    create_dds: bool = False,
) -> dict[str, Any]:
    entry, skins, index = _get_my_skin_entry(skin_key)
    data = start_point if isinstance(start_point, dict) else {}
    mode = str(data.get("mode") or data.get("startPointId") or data.get("id") or "").strip().lower()
    if mode in {"", "blank"}:
        entry["livery_start_point"] = _json_ready({
            **data,
            "mode": livery_start_points.BLANK_START_POINT_ID,
            "id": livery_start_points.BLANK_START_POINT_ID,
            "label": str(data.get("name") or data.get("label") or "Blank livery"),
        })
        skins[index] = entry
        saved = save_my_skins(skins)
        return {
            "mode": "blank",
            "skinKey": skin_key,
            "applied": False,
            "imported": 0,
            "skipped": 0,
            "missing": 0,
            "items": [],
            "reason": "blank start point",
            "registryPath": str(saved),
        }
    if mode != livery_start_points.ORIGINAL_GAME_LIVERY_ID:
        raise ValueError(f"Unsupported livery start point: {mode}")

    if project_types.project_type_for_entry(entry) == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
        return _apply_official_livery_start_point_to_project(
            entry,
            skins,
            index,
            skin_key,
            root,
            data,
            overwrite=overwrite,
            create_dds=create_dds,
        )

    _assert_my_skin_policy(entry, "canUseEditablePreviewTools")
    prepared = prepare_livery_preview_project(skin_key, root)
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    project_folder_text = str(prepared.get("projectFolder") or project.get("projectFolder") or "").strip()
    if not project_folder_text:
        raise RuntimeError("Preview project folder is not available")
    project_folder = Path(project_folder_text)
    game_root = normalize_game_root(root or project.get("gameRoot") or entry.get("game_root") or get_default_root_folder())
    car_id = str(entry.get("car_model") or project.get("carModel") or data.get("carId") or "").strip()
    if not car_id:
        raise RuntimeError("Car id is not available for start point import")
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    source_material = norm_slashes(str(source_skin.get("material") or ""))
    if not source_material:
        raise RuntimeError("Original source material is not available for this livery.")
    source_template = {
        "material": source_material,
        "sourceMaterial": source_material,
        "cardesign": source_skin.get("cardesign") or "",
        "design": source_skin.get("design") or "",
        "visualPreset": source_skin.get("visual_preset") or "",
        "mechanicalPreset": source_skin.get("mechanical_preset") or "",
        "carFinalState": source_skin.get("carfinalstate") or "",
    }

    inspected = livery_start_points.inspect_original_game_livery(
        root=game_root,
        car_id=car_id,
        workflow="advanced",
        template=source_template,
    )
    textures = inspected.get("textures") if isinstance(inspected.get("textures"), list) else []
    results: list[dict[str, Any]] = []
    specs: list[dict[str, Any]] = []
    body_color_mode = _body_base_color_mode_for_livery_preview(
        entry,
        game_root,
        project,
        project_types.project_type_for_entry(entry),
        editable_preview=True,
    )
    preserve_original_detail = bool(body_color_mode.get("enabled"))

    for row in textures:
        if not isinstance(row, dict):
            continue
        stem = str(row.get("slotId") or "").strip().lower()
        if not stem.startswith("ext_skin"):
            continue
        source = _resolve_livery_start_point_texture(game_root, row.get("sourceTexturePath"))
        meta = _ext_skin_project_texture_meta(stem)
        target = _resolve_project_texture_path(project, project_folder, stem)
        spec = {
            "label": meta["label"],
            "stem": stem,
            "png": str(target),
            "texture": str(source or ""),
            "slot": row.get("materialSlot") or "",
            "material": row.get("materialPath") or "",
            "optional": stem not in EXT_SKIN_PRIMARY_STEMS,
            "pathKey": meta["pathKey"],
            "nameKey": meta["nameKey"],
            "projectName": meta["png"],
        }
        if source and source.exists() and source.with_suffix(".texturemips").exists():
            try:
                desc = parse_texture_descriptor(source)
                spec["suggestedFormat"], _flags = choose_auto_format(source, desc, source.with_suffix(".texturemips").stat().st_size, str(row.get("materialSlot") or ""))
            except Exception:
                pass
        specs.append(spec)

        if source is None or not source.exists():
            results.append({"label": meta["label"], "stem": stem, "status": "missing", "source": str(source or ""), "target": str(target), "reason": "source missing"})
            continue
        if not source.with_suffix(".texturemips").exists():
            results.append({"label": meta["label"], "stem": stem, "status": "missing", "source": str(source), "target": str(target), "reason": "texturemips missing"})
            continue
        local_start_point_sync = (
            _sync_original_start_point_texturemips_to_local_skin(entry, stem, source)
            if preserve_original_detail and stem == "ext_skin_d"
            else {}
        )
        replace_neutral_detail_placeholder = bool(
            stem == "ext_skin_d"
            and target.exists()
            and not overwrite
            and _is_solid_black_png(target)
        )
        if target.exists() and not overwrite and not replace_neutral_detail_placeholder and (not create_dds or target.with_suffix(".dds").exists()):
            result_item = {
                "label": meta["label"],
                "stem": stem,
                "status": "skipped",
                "source": str(source),
                "target": str(target),
                "ddsTarget": str(target.with_suffix(".dds")) if target.with_suffix(".dds").exists() else "",
                "reason": "target exists",
            }
            if local_start_point_sync:
                result_item["localStartPointSync"] = local_start_point_sync
            if stem == "ext_skin_c" or (stem == "ext_skin_d" and not _is_solid_black_png(target)):
                base_sync = _sync_livery_start_point_layer_studio_base(project_folder, stem, target)
                if base_sync:
                    result_item["layerStudioBase"] = base_sync
            results.append(result_item)
            continue
        try:
            converted = convert_texture_to_png(source, target, overwrite=overwrite or replace_neutral_detail_placeholder, create_dds=create_dds)
            result_item = {
                "label": meta["label"],
                "stem": stem,
                "status": "imported" if not converted.get("skipped") else "skipped",
                "source": str(source),
                "target": str(target),
                "reason": converted.get("reason") or "",
                "ddsTarget": converted.get("ddsPath") or "",
                "ddsStatus": converted.get("ddsStatus") or "",
            }
            if local_start_point_sync:
                result_item["localStartPointSync"] = local_start_point_sync
            if result_item["status"] == "imported":
                base_sync = _sync_livery_start_point_layer_studio_base(project_folder, stem, target)
                if base_sync:
                    result_item["layerStudioBase"] = base_sync
            results.append(result_item)
        except Exception as exc:
            results.append({
                "label": meta["label"],
                "stem": stem,
                "status": "error",
                "source": str(source),
                "target": str(target),
                "reason": str(exc),
                "warning": str(exc),
            })

    if specs:
        _update_livery_project_material_texture_metadata(prepared, specs)

    start_point_meta = {
        "mode": mode,
        "id": livery_start_points.ORIGINAL_GAME_LIVERY_ID,
        "label": inspected.get("label") or "AC EVO default livery",
        "source": inspected.get("source") or "game_content",
        "appliedAt": datetime.now().isoformat(timespec="seconds"),
    }
    project["liveryStartPoint"] = _json_ready(start_point_meta)
    project["bodyBaseColorMode"] = body_color_mode.get("mode") or BODY_BASE_COLOR_MODE_UNKNOWN
    project["bodyBaseColorEnabled"] = bool(body_color_mode.get("enabled"))
    project["bodyBaseColorCapability"] = _json_ready(body_color_mode)
    _write_livery_preview_project_fields(project.get("projectPath") or prepared.get("projectPath"), {
        "liveryStartPoint": project["liveryStartPoint"],
        "bodyBaseColorMode": project["bodyBaseColorMode"],
        "bodyBaseColorEnabled": project["bodyBaseColorEnabled"],
        "bodyBaseColorCapability": project["bodyBaseColorCapability"],
    })

    imported = sum(1 for item in results if item.get("status") == "imported")
    skipped = sum(1 for item in results if item.get("status") == "skipped")
    missing = sum(1 for item in results if item.get("status") == "missing")
    errors = [item for item in results if item.get("status") == "error"]
    if errors and not imported:
        first = str(errors[0].get("reason") or "start point import failed")
        raise RuntimeError(first)
    return {
        **prepared,
        "mode": mode,
        "skinKey": skin_key,
        "applied": bool(imported or skipped),
        "startPoint": inspected,
        "imported": imported,
        "skipped": skipped,
        "missing": missing,
        "errorCount": len(errors),
        "ddsImported": sum(1 for item in results if item.get("ddsStatus") == "imported"),
        "items": results,
        "warnings": list(dict.fromkeys([
            *[str(row) for row in inspected.get("warnings", []) or []],
            *[str(item.get("warning") or item.get("reason") or "") for item in errors if str(item.get("warning") or item.get("reason") or "").strip()],
        ])),
    }


def _source_numberplate_calibration_keys_by_surface(game_root: Path, entry: dict[str, Any]) -> dict[int, str]:
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    if not source_skin:
        return {}
    source_actor_rel = str(source_skin.get("actor") or "").strip()
    source_actor_path = resolve_content_path(game_root, source_actor_rel) if source_actor_rel else None
    if not source_actor_path or not source_actor_path.exists():
        return {}
    try:
        selector, _selector_source = _source_numberplate_selector(game_root, source_skin)
        selected = _actor_selected_placement_records_by_surface(source_actor_path.read_bytes(), selector)
    except Exception:
        return {}
    keys: dict[int, str] = {}
    for surface, row in selected.items():
        key = _numberplate_calibration_key_for_placement(row)
        if key:
            keys[int(surface)] = key
    return keys


def prepare_livery_numberplate_overlay(skin_key: str, root: str | Path | None = None, overwrite: bool = False) -> dict[str, Any]:
    prepared = prepare_livery_preview_project(skin_key, root)
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    game_root = normalize_game_root(root or project.get("gameRoot") or entry.get("game_root") or DEFAULT_ROOT)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    actor_path = _skin_file_disk_path(game_root, entry, str(files.get("actor") or ""))
    visual_path = _skin_file_disk_path(game_root, entry, str(files.get("visual_preset") or ""))
    if actor_path is None or not actor_path.exists():
        raise FileNotFoundError("Actor file for this skin was not found")
    actor_data = actor_path.read_bytes()
    numberplates = _actor_extract_numberplates(actor_data)
    placements = _actor_extract_numberplate_placements(actor_data)
    variant: dict[str, Any] = {}
    stored_variant = entry.get("numberplate_variant") if isinstance(entry.get("numberplate_variant"), dict) else {}
    if visual_path and visual_path.exists():
        try:
            variant = inspect_visual_numberplate_variant(actor_path, visual_path)
        except Exception:
            variant = {}
    if stored_variant.get("owns_actor_numberplate") is True and str(stored_variant.get("mode") or "").lower() == "split":
        variant = dict(stored_variant)
    elif not variant:
        variant = dict(stored_variant)
    elif stored_variant.get("owns_actor_numberplate") is True:
        variant = _merge_stored_numberplate_variant_metadata(stored_variant, variant)
        try:
            inspected_index = int(variant.get("numberplate_index"))
            stored_index = int(stored_variant.get("numberplate_index"))
        except Exception:
            inspected_index = -1
            stored_index = -2
        if inspected_index == stored_index:
            variant["created_by"] = stored_variant.get("created_by") or "LiveryLab"
            variant["owns_actor_numberplate"] = True
    index = variant.get("numberplate_index")
    if index is None:
        index = 0
    try:
        plate_index = max(0, min(int(index), len(numberplates) - 1))
    except Exception:
        plate_index = 0
    selected_plate = numberplates[plate_index] if numberplates else {}
    background_rel = str(variant.get("background_path") or selected_plate.get("background_path") or "").strip()
    font_rel = str(variant.get("font_path") or selected_plate.get("font_path") or "").strip()
    placement_sources = variant.get("placement_sources") if isinstance(variant.get("placement_sources"), dict) else {}
    source_keys_by_surface = _source_numberplate_calibration_keys_by_surface(game_root, entry) if variant.get("owns_actor_numberplate") is True else {}
    placement_calibrations_by_surface = _numberplate_placement_calibrations_for_actor(actor_path) if variant.get("owns_actor_numberplate") is True else {}
    owned_indices_1based = {index + 1 for index in _numberplate_variant_indices(variant)} if variant.get("owns_actor_numberplate") is True else set()
    numberplate_project_dir = (
        Path(str(project.get("projectFolder") or _livery_project_folder_for_entry(entry)))
        / "preview_assets"
        / "numberplates"
    )
    bake = entry.get("numberplate_bake") if isinstance(entry.get("numberplate_bake"), dict) else {}
    baked_surfaces = bake.get("surfaces") if isinstance(bake.get("surfaces"), dict) else {}
    baked_rels = {
        normalize_game_rel_path(str(surface.get("textureRel") or "")).lower(): surface
        for surface in baked_surfaces.values()
        if isinstance(surface, dict) and str(surface.get("textureRel") or "").strip()
    }
    first_baked_surface = next((surface for surface in baked_surfaces.values() if isinstance(surface, dict)), {})
    background_texture = resolve_content_path(game_root, background_rel) if background_rel else None
    font_texture = resolve_content_path(game_root, font_rel) if font_rel else None
    background_png = numberplate_project_dir / "numberplate_background.png"
    font_png = numberplate_project_dir / "numberplate_font.png"
    background_status = "missing"
    selected_baked = baked_rels.get(normalize_game_rel_path(background_rel).lower()) if background_rel else None
    if selected_baked:
        baked_png = _numberplate_baked_surface_png_path(entry, selected_baked)
        background_png = baked_png if baked_png and baked_png.exists() else background_png
        background_status = "baked" if baked_png and baked_png.exists() else "missing"
    elif baked_surfaces and first_baked_surface:
        baked_png = _numberplate_baked_surface_png_path(entry, first_baked_surface)
        background_png = baked_png if baked_png and baked_png.exists() else background_png
        background_status = "baked" if baked_png and baked_png.exists() else "missing"
    elif background_texture and background_texture.exists():
        try:
            converted = convert_texture_to_png(background_texture, background_png, overwrite=overwrite, create_dds=False)
            background_status = "imported" if not converted.get("skipped") else "skipped"
        except Exception as exc:
            background_status = f"failed: {exc}"
    font_status = "missing"
    if baked_surfaces:
        font_status = "not used"
    elif font_texture and font_texture.exists():
        try:
            converted = convert_texture_to_png(font_texture, font_png, overwrite=overwrite, create_dds=False)
            font_status = "imported" if not converted.get("skipped") else "skipped"
        except Exception as exc:
            font_status = f"failed: {exc}"
    placement_background_cache: dict[str, dict[str, str]] = {}
    placement_font_cache: dict[str, dict[str, str]] = {}

    def numberplate_text_color(plate: dict[str, Any]) -> list[float] | None:
        overlay = plate.get("overlay") if isinstance(plate, dict) else None
        if not isinstance(overlay, list) or len(overlay) < 4:
            return None
        values: list[float] = []
        for value in overlay[:4]:
            try:
                values.append(float(value))
            except Exception:
                return None
        return values

    def placement_font(rel: str, label: str, actor_index: str) -> dict[str, str]:
        rel = str(rel or "").strip()
        if not rel:
            return {"fontRel": "", "fontPath": "", "fontPng": "", "fontStatus": "missing"}
        texture_path = resolve_content_path(game_root, rel)
        normalized_rel = rel.replace("/", "\\")
        if normalized_rel in placement_font_cache:
            return dict(placement_font_cache[normalized_rel])
        rel_hash = hashlib.sha1(normalized_rel.lower().encode("utf-8", errors="ignore")).hexdigest()[:8]
        png_path = numberplate_project_dir / f"numberplate_font_{sanitize_slug(label).lower() or 'placement'}_{actor_index}_{rel_hash}.png"
        status = "missing"
        if texture_path.exists():
            try:
                converted = convert_texture_to_png(texture_path, png_path, overwrite=overwrite, create_dds=False)
                status = "imported" if not converted.get("skipped") else "skipped"
            except Exception as exc:
                status = f"failed: {exc}"
        result = {
            "fontRel": rel,
            "fontPath": str(texture_path) if texture_path.exists() else "",
            "fontPng": str(png_path) if png_path.exists() else "",
            "fontStatus": status,
        }
        placement_font_cache[normalized_rel] = dict(result)
        return result

    def placement_background(row: dict[str, Any]) -> dict[str, str]:
        try:
            numberplate_index_1based = int(row.get("field_2") or 0)
        except Exception:
            numberplate_index_1based = 0
        numberplate_index = numberplate_index_1based - 1
        plate = numberplates[numberplate_index] if 0 <= numberplate_index < len(numberplates) else {}
        rel = str(plate.get("background_path") or "").strip()
        if not rel:
            return {"backgroundRel": "", "backgroundPath": "", "backgroundPng": "", "backgroundStatus": "missing"}
        try:
            surface_key = _numberplate_template_surface_key(int(row.get("outer_field_number") or row.get("surface") or 0))
        except Exception:
            surface_key = ""
        baked_surface = baked_surfaces.get(surface_key) if isinstance(baked_surfaces.get(surface_key), dict) else {}
        baked_rel = normalize_game_rel_path(str(baked_surface.get("textureRel") or ""))
        if baked_surface:
            png_path = _numberplate_baked_surface_png_path(entry, baked_surface)
            texture_path = (_local_skin_asset_path(entry, baked_rel) if baked_rel else None) or resolve_content_path(game_root, baked_rel or rel)
            return {
                "backgroundRel": baked_rel or rel,
                "backgroundPath": str(texture_path) if texture_path and texture_path.exists() else "",
                "backgroundPng": str(png_path) if png_path and png_path.exists() else "",
                "backgroundStatus": "baked" if png_path and png_path.exists() else "missing",
            }
        texture_path = resolve_content_path(game_root, rel)
        label = sanitize_slug(str(row.get("label") or "placement")).lower() or "placement"
        actor_index = str(row.get("index") if row.get("index") is not None else numberplate_index_1based)
        normalized_rel = rel.replace("/", "\\")
        cache_key = f"{normalized_rel}|{label}|{actor_index}"
        if cache_key in placement_background_cache:
            return dict(placement_background_cache[cache_key])
        png_path = numberplate_project_dir / f"numberplate_{label}_{actor_index}.png"
        status = "missing"
        if texture_path.exists():
            try:
                converted = convert_texture_to_png(texture_path, png_path, overwrite=overwrite, create_dds=False)
                status = "imported" if not converted.get("skipped") else "skipped"
            except Exception as exc:
                status = f"failed: {exc}"
        result = {
            "backgroundRel": rel,
            "backgroundPath": str(texture_path) if texture_path.exists() else "",
            "backgroundPng": str(png_path) if png_path.exists() else "",
            "backgroundStatus": status,
        }
        placement_background_cache[cache_key] = dict(result)
        return result

    normalized_placements = []
    for row in placements:
        background = placement_background(row)
        try:
            row_numberplate_index_for_font = int(row.get("field_2") or 0) - 1
        except Exception:
            row_numberplate_index_for_font = -1
        row_plate = numberplates[row_numberplate_index_for_font] if 0 <= row_numberplate_index_for_font < len(numberplates) else {}
        row_label = sanitize_slug(str(row.get("label") or "placement")).lower() or "placement"
        row_actor_index = str(row.get("index") if row.get("index") is not None else row.get("field_2") or 0)
        if background.get("backgroundStatus") == "baked":
            font = {"fontRel": "", "fontPath": "", "fontPng": "", "fontStatus": "not used"}
            text_color = None
        else:
            font = placement_font(str(row_plate.get("font_path") or font_rel or "").strip(), row_label, row_actor_index)
            text_color = numberplate_text_color(row_plate) or numberplate_text_color(selected_plate)
        row_index_key = str(row.get("index"))
        source_info = placement_sources.get(row_index_key) if isinstance(placement_sources.get(row_index_key), dict) else {}
        source_calibration_key = str(source_info.get("source_calibration_key") or "").strip()
        source_synthetic = source_info.get("synthetic") is True
        try:
            row_surface = int(row.get("outer_field_number") or row.get("surface") or 0)
            row_numberplate_index = int(row.get("field_2") or row.get("numberplate_index") or 0)
        except Exception:
            row_surface = 0
            row_numberplate_index = 0
        actor_baseline: dict[str, Any] = {}
        if source_synthetic and row_surface:
            surface_calibration = placement_calibrations_by_surface.get(row_surface) if isinstance(placement_calibrations_by_surface.get(row_surface), dict) else {}
            actor_baseline = surface_calibration.get("actorBaseline") if isinstance(surface_calibration.get("actorBaseline"), dict) else {}
        if source_synthetic and row_surface == 1:
            source_calibration_key = "HOOD"
        if not source_calibration_key and row_surface and row_numberplate_index in owned_indices_1based:
            source_calibration_key = source_keys_by_surface.get(row_surface, "")
        normalized_placements.append({
            "index": row.get("index"),
            "surface": row.get("outer_field_number"),
            "label": row.get("label") or f"Placement {row.get('index')}",
            "numberplate_index": row.get("field_2"),
            "field_1": row.get("field_1"),
            "field_2": row.get("field_2"),
            "opacity": row_plate.get("opacity") if row_plate.get("opacity") is not None else 1.0,
            "position": row.get("position") or [0, 0, 0],
            "rotation": row.get("rotation") or [0, 0, 0],
            "scale": row.get("scale") or [1, 1, 1],
            "sourceCalibrationKey": source_calibration_key,
            "sourcePlacementIndex": source_info.get("source_index"),
            "sourceSurface": source_info.get("source_surface"),
            "sourceSynthetic": source_synthetic,
            "synthetic": source_synthetic,
            "generatedSurface": source_info.get("generated_surface"),
            "generatedLabel": source_info.get("generated_label"),
            "actorBaseline": _json_ready(actor_baseline) if actor_baseline else {},
            "textColor": text_color,
            **background,
            **font,
        })
    selected_placements: list[dict[str, Any]] = []
    selector = variant.get("selector") if isinstance(variant.get("selector"), dict) else {}
    if selector:
        by_surface: dict[int, list[dict[str, Any]]] = {}
        for row in normalized_placements:
            try:
                surface = int(row.get("surface") or 0)
            except Exception:
                surface = 0
            if surface:
                by_surface.setdefault(surface, []).append(row)
        for surface_raw, ordinal_raw in selector.items():
            try:
                surface = int(surface_raw)
                ordinal = int(ordinal_raw)
            except Exception:
                continue
            rows = by_surface.get(surface, [])
            if 0 < ordinal <= len(rows):
                selected_placements.append(rows[ordinal - 1])
    if not selected_placements and plate_index is not None:
        selected_placements = [
            row for row in normalized_placements
            if int(row.get("numberplate_index") or 0) == int(plate_index) + 1
        ]
    return {
        **prepared,
        "actorPath": actor_path,
        "visualPath": visual_path or "",
        "numberplates": numberplates,
        "placements": selected_placements,
        "allPlacements": normalized_placements,
        "placementFilter": {
            "selector": _json_ready(selector),
            "selected": len(selected_placements),
            "total": len(normalized_placements),
        },
        "numberplateOverlay": _json_ready(project.get("numberplateOverlay") if isinstance(project.get("numberplateOverlay"), dict) else {}),
        "variant": _json_ready(variant),
        "selectedNumberplate": _json_ready(selected_plate),
        "selectedNumberplateIndex": plate_index,
        "backgroundPath": str(background_texture) if background_texture else "",
        "backgroundRel": background_rel,
        "backgroundPng": str(background_png) if background_png.exists() else "",
        "backgroundStatus": background_status,
        "fontRel": font_rel,
        "fontPath": str(font_texture) if font_texture else "",
        "fontPng": str(font_png) if font_png.exists() else "",
        "fontStatus": font_status,
        "textColor": numberplate_text_color(selected_plate),
    }


def _numberplate_placement_save_key(placement: dict[str, Any], fallback_index: int | None = None) -> str:
    label = str(placement.get("label") or "Placement").strip() or "Placement"
    index = placement.get("index")
    if index is None:
        index = fallback_index if fallback_index is not None else ""
    return f"{label}:{index}"


def _coerce_preview_numberplate_transform(transform: dict[str, Any]) -> dict[str, float]:
    values: dict[str, float] = {}
    defaults = {"x": 0.0, "y": 0.0, "z": 0.0, "rx": 0.0, "ry": 0.0, "rz": 0.0, "sx": 1.0, "sy": 1.0}
    for key, fallback in defaults.items():
        try:
            value = float(transform.get(key, fallback))
        except Exception:
            value = fallback
        values[key] = value
    return values


def _actor_placement_from_preview_transform(placement: dict[str, Any], transform: dict[str, float]) -> dict[str, Any]:
    existing_scale = placement.get("scale") if isinstance(placement.get("scale"), list) else []
    scale_z = transform["sx"]
    if len(existing_scale) >= 3 and existing_scale[2] not in (None, ""):
        try:
            scale_z = float(existing_scale[2])
        except Exception:
            scale_z = transform["sx"]
    return {
        "index": int(placement.get("index")),
        "position": [transform["x"], transform["y"], transform["z"]],
        "rotation": [transform["rx"], transform["ry"], transform["rz"]],
        "scale": [transform["sx"], transform["sy"], scale_z],
    }


def _copy_actor_vector(values: Any, size: int = 3) -> list[float | None]:
    source = values if isinstance(values, list) else []
    result: list[float | None] = []
    for index in range(size):
        value = source[index] if index < len(source) else None
        if value is None or value == "":
            result.append(None)
            continue
        try:
            result.append(float(value))
        except Exception:
            result.append(None)
    return result


def nudge_livery_numberplate_actor_placement(skin_key: str, root: str | Path | None, placement: dict[str, Any], vector: str, axis: int, delta: float) -> dict[str, Any]:
    prepared = prepare_livery_numberplate_overlay(skin_key, root, overwrite=False)
    variant = prepared.get("variant") if isinstance(prepared.get("variant"), dict) else {}
    if variant.get("owns_actor_numberplate") is not True:
        raise RuntimeError("Actor nudge is only enabled for LiveryLab-owned custom numberplate variants.")
    if not isinstance(placement, dict) or placement.get("index") is None:
        raise ValueError("Numberplate actor placement index is required")
    actor_path = Path(str(prepared.get("actorPath") or ""))
    if not actor_path.exists():
        raise FileNotFoundError("Actor file for this skin was not found")
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    game_root = normalize_game_root(root or project.get("gameRoot") or DEFAULT_ROOT)
    require_game_write_ready(game_root, "nudge livery numberplate actor placement", [actor_path])

    vector_key = str(vector or "").strip().lower()
    if vector_key not in {"position", "rotation", "scale"}:
        raise ValueError("Actor vector must be position, rotation, or scale")
    axis_index = int(axis)
    if axis_index < 0 or axis_index > 2:
        raise ValueError("Actor vector axis must be 0, 1, or 2")
    delta_value = float(delta)
    if delta_value == 0:
        raise ValueError("Actor nudge delta must not be 0")

    requested_actor_index = int(placement.get("index"))
    all_placements = prepared.get("allPlacements") if isinstance(prepared.get("allPlacements"), list) else []
    actor_placement = None
    for row in all_placements:
        try:
            row_index = int(row.get("index", -1))
        except Exception:
            row_index = -1
        if row_index == requested_actor_index:
            actor_placement = row
            break
    if not actor_placement:
        raise ValueError(f"Actor placement {requested_actor_index} was not found")

    owned_numberplate_indices_1based = {
        int(index) + 1
        for index in _numberplate_variant_indices(variant)
        if int(index) >= 0
    }
    if not owned_numberplate_indices_1based:
        owned_numberplate_indices_1based = {int(prepared.get("selectedNumberplateIndex") or 0) + 1}
    try:
        placement_numberplate_index = int(actor_placement.get("numberplate_index") or actor_placement.get("field_2") or 0)
    except Exception:
        placement_numberplate_index = 0
    if placement_numberplate_index not in owned_numberplate_indices_1based:
        try:
            requested_surface = int(actor_placement.get("surface") or actor_placement.get("outer_field_number") or placement.get("surface") or placement.get("outer_field_number") or 0)
        except Exception:
            requested_surface = 0
        requested_label = str(actor_placement.get("label") or placement.get("label") or "").strip().upper()
        active_candidate = None
        for row in all_placements:
            try:
                row_numberplate_index = int(row.get("numberplate_index") or row.get("field_2") or 0)
                row_surface = int(row.get("surface") or row.get("outer_field_number") or 0)
            except Exception:
                continue
            row_label = str(row.get("label") or "").strip().upper()
            if row_numberplate_index not in owned_numberplate_indices_1based:
                continue
            if requested_surface and row_surface != requested_surface:
                continue
            if requested_label and row_label and row_label != requested_label:
                continue
            active_candidate = row
            break
        if active_candidate is None:
            raise RuntimeError("Selected placement does not belong to the active custom numberplate variant.")
        actor_placement = active_candidate
    actor_index = int(actor_placement.get("index"))

    before_vector = _copy_actor_vector(actor_placement.get(vector_key), 3)
    if before_vector[axis_index] is None:
        raise RuntimeError(
            f"Actor field {vector_key}[{axis_index}] is not present on this placement. "
            "Nudge will not create missing actor fields."
        )
    after_vector = list(before_vector)
    after_vector[axis_index] = float(after_vector[axis_index]) + delta_value
    update_payload = {
        "index": actor_index,
        vector_key: after_vector,
    }
    actor_changed, actor_backup = update_actor_numberplate_file(actor_path, placements=[update_payload])
    if not actor_changed:
        raise RuntimeError("Actor nudge did not change the actor file")
    entry, skins, entry_index = _get_my_skin_entry(skin_key)
    _mark_my_skin_game_sync_required(entry, "Numberplate actor placement changed", "nudge_livery_numberplate_actor_placement")
    skins[entry_index] = entry
    save_my_skins(skins)

    refreshed = prepare_livery_numberplate_overlay(skin_key, root, overwrite=False)
    updated_placement = None
    for row in refreshed.get("allPlacements", []) if isinstance(refreshed.get("allPlacements"), list) else []:
        try:
            if int(row.get("index", -1)) == actor_index:
                updated_placement = row
                break
        except Exception:
            continue

    return {
        "actorPath": str(actor_path),
        "actorBackup": str(actor_backup) if actor_backup else "",
        "actorChanged": actor_changed,
        "placementIndex": actor_index,
        "vector": vector_key,
        "axis": axis_index,
        "delta": delta_value,
        "before": _json_ready(actor_placement),
        "after": _json_ready(updated_placement or {}),
        "placements": _json_ready(refreshed.get("placements") or []),
        "allPlacements": _json_ready(refreshed.get("allPlacements") or []),
    }


def restore_livery_numberplate_actor_placement_from_base(skin_key: str, root: str | Path | None, placement: dict[str, Any]) -> dict[str, Any]:
    if not isinstance(placement, dict) or placement.get("index") is None:
        raise ValueError("Numberplate actor placement index is required")
    result = reset_livery_numberplate_actor_placements_from_source(skin_key, root, placement=placement)
    result["placementIndex"] = int(placement.get("index"))
    return result


def _numberplate_surface_kind(placement: dict[str, Any]) -> str:
    label = str(placement.get("label") or "").upper()
    try:
        surface = int(placement.get("surface") or placement.get("outer_field_number") or 0)
    except Exception:
        surface = 0
    if "HOOD" in label or surface == 1:
        return "hood"
    if "DOOR" in label or surface in {3, 4}:
        return "door"
    return "other"


def _preview_numberplate_transform_looks_like_actor_raw(
    placement: dict[str, Any],
    transform: dict[str, Any],
) -> bool:
    if _numberplate_surface_kind(placement) != "door":
        return False

    def _optional_float(value: Any) -> float | None:
        try:
            number = float(value)
        except Exception:
            return None
        return number if math.isfinite(number) else None

    def _close(left: Any, right: Any, tolerance: float = 0.001) -> bool:
        a = _optional_float(left)
        b = _optional_float(right)
        return a is not None and b is not None and abs(a - b) <= tolerance

    rotation = placement.get("rotation") if isinstance(placement.get("rotation"), list) else []
    scale = placement.get("scale") if isinstance(placement.get("scale"), list) else []
    rz = _optional_float(transform.get("rz"))
    sy = _optional_float(transform.get("sy"))
    x = _optional_float(transform.get("x"))
    actor_rz = _optional_float(rotation[2] if len(rotation) > 2 else None)
    actor_depth = _optional_float(scale[1] if len(scale) > 1 else None)
    has_actor_roll = rz is not None and abs(rz) >= 45
    matches_actor_roll = actor_rz is not None and rz is not None and _close(rz, actor_rz, 1)
    matches_actor_depth = (
        actor_depth is not None
        and sy is not None
        and actor_depth <= 0.35
        and _close(abs(sy), abs(actor_depth), 0.025)
    )
    centered_like_actor = x is None or abs(x) < 0.08
    return (has_actor_roll and matches_actor_depth) or (matches_actor_roll and centered_like_actor)


def _numberplate_export_factors(calibration: dict[str, Any], surface_kind: str) -> dict[str, float]:
    defaults_by_surface = {
        "door": {
            "position_y": 0.5,
            "position_z": 0.5,
            "rotation_y": 1.0,
            "scale_x": 1.0,
            "scale_z": 1.0,
        },
        "hood": {
            "position_x": 0.5,
            "position_y": 0.5,
            "position_z": 0.5,
            "rotation_x": 1.0,
            "rotation_y": 1.0,
            "scale_x": 1.0,
            "scale_z": 1.0,
        },
    }
    factors = dict(defaults_by_surface.get(surface_kind, {}))
    raw_factors = calibration.get("exportFactors") if isinstance(calibration, dict) and isinstance(calibration.get("exportFactors"), dict) else {}
    for key, value in raw_factors.items():
        if key not in factors or value in (None, ""):
            continue
        try:
            factors[key] = float(value)
        except Exception:
            pass
    return factors


def _numberplate_hood_rotation_axis(calibration: dict[str, Any], base_rotation: list[float | None]) -> tuple[str | None, int | None, str | None]:
    raw_factors = calibration.get("exportFactors") if isinstance(calibration, dict) and isinstance(calibration.get("exportFactors"), dict) else {}
    preferred_key = ""
    if raw_factors.get("rotation_y") not in (None, ""):
        preferred_key = "rotation_y"
    elif raw_factors.get("rotation_x") not in (None, ""):
        preferred_key = "rotation_x"

    preferred_index = 1 if preferred_key == "rotation_y" else 0
    if preferred_key:
        if base_rotation[preferred_index] is not None:
            return preferred_key, preferred_index, None
        return None, None, f"actor baseline {preferred_key.replace('_', '.')} is not present; hood rotation was skipped"
    if base_rotation[1] is not None:
        return "rotation_y", 1, None
    return None, None, "hood rotation axis is not calibrated; hood rotation was skipped"


def _numberplate_preview_rotation_delta(preview_delta: dict[str, float], calibration: dict[str, Any], surface_kind: str) -> tuple[float, str]:
    raw_key = str(calibration.get("previewRotationKey") or calibration.get("preview_rotation_key") or "").strip().lower()
    if raw_key in {"rx", "ry", "rz"}:
        return float(preview_delta.get(raw_key, 0.0)), raw_key
    if surface_kind == "hood" and abs(float(preview_delta.get("ry", 0.0))) < 0.000001 and abs(float(preview_delta.get("rz", 0.0))) > 0.000001:
        return float(preview_delta.get("rz", 0.0)), "rz"
    return float(preview_delta.get("ry" if surface_kind == "hood" else "rx", 0.0)), "ry" if surface_kind == "hood" else "rx"


def _append_skipped_actor_axis_warning(warnings: list[str], label: str) -> None:
    warnings.append(f"{label} is not present in the actor; preview delta for this field was skipped")


def _numberplate_generated_from_fit(calibration: dict[str, Any] | None) -> bool:
    if not isinstance(calibration, dict):
        return False
    return calibration.get("generatedFromFit") is True or calibration.get("generated_from_fit") is True


def _numberplate_generated_door_rotation_y(
    placement: dict[str, Any],
    current_transform: dict[str, float],
    base_transform: dict[str, float],
    source_placement: dict[str, Any] | None = None,
    calibration: dict[str, Any] | None = None,
    rotation_factor: float = 1.0,
) -> tuple[float, str]:
    label = str(placement.get("label") or "").upper()
    try:
        surface = int(placement.get("surface") or placement.get("outer_field_number") or 0)
    except Exception:
        surface = 0
    target_rotation = _copy_actor_vector(placement.get("rotation"), 3)
    source_rotation = _copy_actor_vector((source_placement or {}).get("rotation"), 3)
    try:
        source_index = int((source_placement or {}).get("index"))
        placement_index = int(placement.get("index"))
    except Exception:
        source_index = None
        placement_index = None
    if source_rotation[1] is not None and source_index != placement_index:
        side_base = source_rotation[1]
        mode = "source-fit-stable-baseline"
    else:
        side_base = target_rotation[1]
        mode = "target-fit-baseline"
    if side_base is None:
        side_base = -90.0 if surface == 4 or "_R" in label or label.endswith("R") else 90.0
        mode = f"{mode}-fallback"
    return side_base + (float(current_transform["ry"]) - float(base_transform["ry"])) * float(rotation_factor), mode


def _numberplate_actor_baseline(
    calibration: dict[str, Any],
    fallback_placement: dict[str, Any],
) -> tuple[list[float | None], list[float | None], list[float | None], str]:
    raw = calibration.get("actorBaseline") if isinstance(calibration, dict) and isinstance(calibration.get("actorBaseline"), dict) else {}
    fallback_position = _copy_actor_vector(fallback_placement.get("position"), 3)
    fallback_rotation = _copy_actor_vector(fallback_placement.get("rotation"), 3)
    fallback_scale = _copy_actor_vector(fallback_placement.get("scale"), 3)
    if not raw:
        return fallback_position, fallback_rotation, fallback_scale, "current"

    def value(key: str, fallback: float | None) -> float | None:
        raw_value = raw.get(key)
        if raw_value in (None, ""):
            return fallback
        try:
            return float(raw_value)
        except Exception:
            return fallback

    position = [
        value("position_x", fallback_position[0]),
        value("position_y", fallback_position[1]),
        value("position_z", fallback_position[2]),
    ]
    rotation = [
        value("rotation_x", fallback_rotation[0]),
        value("rotation_y", fallback_rotation[1]),
        value("rotation_z", fallback_rotation[2]),
    ]
    scale = [
        value("scale_x", fallback_scale[0]),
        value("scale_y", fallback_scale[1]),
        value("scale_z", fallback_scale[2]),
    ]
    return position, rotation, scale, "calibration"


def _numberplate_apply_calibration_for_placement(calibration: dict[str, Any] | None, placement: dict[str, Any]) -> dict[str, Any]:
    result = dict(calibration) if isinstance(calibration, dict) else {}
    actor_baseline = result.get("actorBaseline") if isinstance(result.get("actorBaseline"), dict) else {}
    placement_baseline = placement.get("actorBaseline") if isinstance(placement.get("actorBaseline"), dict) else {}
    if not actor_baseline and placement_baseline:
        actor_baseline = dict(placement_baseline)
        result["actorBaseline"] = actor_baseline

    if _numberplate_surface_kind(placement) == "hood" and actor_baseline:
        export_factors = dict(result.get("exportFactors")) if isinstance(result.get("exportFactors"), dict) else {}
        has_rotation_x = export_factors.get("rotation_x") not in (None, "")
        has_rotation_y = export_factors.get("rotation_y") not in (None, "")
        baseline_x = actor_baseline.get("rotation_x")
        baseline_y = actor_baseline.get("rotation_y")
        if baseline_y in (None, "") and baseline_x not in (None, ""):
            if has_rotation_y and not has_rotation_x:
                export_factors.pop("rotation_y", None)
                export_factors["rotation_x"] = 1.0
            elif not has_rotation_x and not has_rotation_y:
                export_factors["rotation_x"] = 1.0
        elif baseline_y not in (None, "") and not has_rotation_x and not has_rotation_y:
            export_factors["rotation_y"] = 1.0
        if export_factors:
            result["exportFactors"] = export_factors
    return result


def _find_numberplate_base_placement_for_surface(all_placements: list[dict[str, Any]], target_surface: int, target_index: int | None = None) -> dict[str, Any] | None:
    first_match: dict[str, Any] | None = None
    for row in all_placements:
        try:
            surface = int(row.get("surface") or row.get("outer_field_number") or 0)
            row_index = int(row.get("index", -1))
        except Exception:
            continue
        if surface != int(target_surface):
            continue
        if first_match is None:
            first_match = row
        if target_index is not None and row_index == int(target_index):
            continue
        return row
    return first_match


def _find_numberplate_source_placement(
    all_placements: list[dict[str, Any]],
    target_placement: dict[str, Any],
) -> dict[str, Any] | None:
    if target_placement.get("sourceSynthetic") is True or target_placement.get("synthetic") is True:
        return None
    raw_index = target_placement.get("sourcePlacementIndex")
    if raw_index in (None, ""):
        raw_index = target_placement.get("source_index")
    if raw_index in (None, ""):
        return None
    try:
        source_index = int(raw_index)
    except Exception:
        return None
    for row in all_placements:
        try:
            if int(row.get("index", -1)) == source_index:
                return row
        except Exception:
            continue
    return None


def _numberplate_calibration_key_for_placement(placement: dict[str, Any] | None) -> str:
    if not isinstance(placement, dict):
        return ""
    label = str(placement.get("label") or "").strip().upper()
    index = placement.get("index")
    if not label or index is None:
        return ""
    try:
        return f"{label}:{int(index)}"
    except Exception:
        return f"{label}:{index}"


def _numberplate_placement_source_info(source_record: dict[str, Any], surface: int) -> dict[str, Any]:
    return {
        "source_index": source_record.get("index"),
        "source_label": source_record.get("label"),
        "source_surface": source_record.get("outer_field_number") or source_record.get("surface") or int(surface),
        "source_numberplate_index": source_record.get("field_2") or source_record.get("numberplate_index"),
        "source_calibration_key": _numberplate_calibration_key_for_placement(source_record),
    }


def _coerce_numberplate_selector(raw: Any) -> dict[int, int]:
    if not isinstance(raw, dict):
        return {}
    selector: dict[int, int] = {}
    for surface_raw, ordinal_raw in raw.items():
        try:
            surface = int(surface_raw)
            ordinal = int(ordinal_raw)
        except Exception:
            continue
        if surface > 0 and ordinal > 0:
            selector[surface] = ordinal
    return selector


def _source_numberplate_selector(game_root: Path, source_skin: dict[str, Any]) -> tuple[dict[int, int], str]:
    visual_rel = str(source_skin.get("visual_preset") or "").strip()
    visual_path = _disk_from_game_rel(game_root, visual_rel)
    if visual_path and visual_path.exists():
        selector = extract_visual_numberplate_selector(visual_path.read_bytes())
        if selector:
            return selector, str(visual_path)
    selector = _coerce_numberplate_selector(source_skin.get("numberplate_selector"))
    return selector, "source_skin.numberplate_selector" if selector else ""


def _actor_reset_owned_placements_from_source(
    actor_data: bytes,
    source_actor_data: bytes,
    source_selector: dict[int, int],
    owned_indices: list[int],
    target_indices: set[int] | None = None,
    synthetic_calibrations_by_surface: dict[int, dict[str, Any]] | None = None,
) -> tuple[bytes, dict[str, Any]]:
    owned_one = {int(index) + 1 for index in owned_indices if int(index) >= 0}
    if not owned_one:
        return actor_data, {"changed": False, "restored": [], "warnings": ["skin has no owned numberplate indices"]}
    source_raw_by_surface = _actor_selected_placement_raw_by_surface(source_actor_data, source_selector)
    if not source_raw_by_surface:
        return actor_data, {"changed": False, "restored": [], "warnings": ["source visual does not select any actor placements"]}

    actor_fields, payload_index, payload_fields = _actor_payload_fields_for_update(actor_data)
    changed = False
    restored: list[dict[str, Any]] = []
    warnings: list[str] = []
    matched_targets: set[int] = set()
    new_payload_fields: list[ProtoField] = []
    placement_index = 0
    for payload_field in payload_fields:
        if payload_field.number != 52 or payload_field.wire != 2:
            new_payload_fields.append(payload_field)
            continue
        placement_fields = parse_fields(payload_field.value)
        new_placement_fields: list[ProtoField] = []
        for placement_field in placement_fields:
            if placement_field.wire != 2:
                new_placement_fields.append(placement_field)
                continue
            raw = bytes(placement_field.value)
            surface = int(placement_field.number)
            numberplate_index_1 = _actor_placement_numberplate_index_1based(raw)
            label = _actor_placement_label_from_raw(raw)
            should_restore = numberplate_index_1 in owned_one and (target_indices is None or placement_index in target_indices)
            if should_restore:
                matched_targets.add(placement_index)
                source_raw = source_raw_by_surface.get(surface)
                if not source_raw:
                    synthetic_calibration = (synthetic_calibrations_by_surface or {}).get(surface)
                    if surface == 1 and isinstance(synthetic_calibration, dict) and isinstance(synthetic_calibration.get("actorBaseline"), dict):
                        restored_raw = _actor_synthetic_hood_placement_entry(numberplate_index_1, synthetic_calibration)
                        if restored_raw != raw:
                            changed = True
                            new_placement_fields.append(ProtoField(placement_field.number, placement_field.wire, restored_raw))
                        else:
                            new_placement_fields.append(placement_field)
                        restored.append(
                            {
                                "index": placement_index,
                                "surface": surface,
                                "label": label,
                                "source": "synthetic actor baseline",
                            }
                        )
                    else:
                        warnings.append(f"no source placement selected for surface {surface} ({label or placement_index})")
                        new_placement_fields.append(placement_field)
                else:
                    restored_raw = _actor_clone_placement_entry(source_raw, numberplate_index_1)
                    if restored_raw != raw:
                        changed = True
                        new_placement_fields.append(ProtoField(placement_field.number, placement_field.wire, restored_raw))
                    else:
                        new_placement_fields.append(placement_field)
                    restored.append(
                        {
                            "index": placement_index,
                            "surface": surface,
                            "label": label,
                            "numberplateIndex1": numberplate_index_1,
                            "changed": restored_raw != raw,
                        }
                    )
            else:
                new_placement_fields.append(placement_field)
            placement_index += 1
        new_payload_fields.append(ProtoField(payload_field.number, payload_field.wire, build_fields(new_placement_fields)))

    if target_indices:
        for missing in sorted(target_indices - matched_targets):
            warnings.append(f"target placement {missing} was not restored")
    if not changed:
        return actor_data, {"changed": False, "restored": restored, "warnings": warnings}
    new_actor_fields = list(actor_fields)
    new_actor_fields[payload_index] = ProtoField(103, 2, build_fields(new_payload_fields))
    return build_fields(new_actor_fields), {"changed": True, "restored": restored, "warnings": warnings}


def _remove_numberplate_overlay_entries(project_path: Path, actor_indices: set[int]) -> tuple[Path | None, dict[str, Any]]:
    if not actor_indices or not project_path.exists():
        return None, {"changed": False, "removed": []}
    try:
        data = json.loads(project_path.read_text(encoding="utf-8"))
    except Exception:
        return None, {"changed": False, "removed": [], "warning": "preview_project.json could not be read"}
    if not isinstance(data, dict):
        return None, {"changed": False, "removed": []}
    overlay = data.get("numberplateOverlay") if isinstance(data.get("numberplateOverlay"), dict) else {}
    placements = overlay.get("placements") if isinstance(overlay.get("placements"), dict) else {}
    if not placements:
        return None, {"changed": False, "removed": []}
    removed: list[str] = []
    kept: dict[str, Any] = {}
    for key, value in placements.items():
        actor_index = None
        if isinstance(value, dict):
            try:
                actor_index = int(value.get("actorIndex"))
            except Exception:
                actor_index = None
        if actor_index is None:
            try:
                actor_index = int(str(key).rsplit(":", 1)[-1])
            except Exception:
                actor_index = None
        if actor_index in actor_indices:
            removed.append(str(key))
        else:
            kept[str(key)] = value
    if not removed:
        return None, {"changed": False, "removed": []}
    overlay["placements"] = kept
    overlay["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    data["numberplateOverlay"] = overlay
    data["updatedAt"] = overlay["updatedAt"]
    backup = safe_write_text(project_path, json.dumps(_json_ready(data), indent=2, ensure_ascii=False))
    return backup, {"changed": True, "removed": removed}


def reset_livery_numberplate_actor_placements_from_source(
    skin_key: str,
    root: str | Path | None = None,
    placement: dict[str, Any] | None = None,
) -> dict[str, Any]:
    prepared = prepare_livery_numberplate_overlay(skin_key, root, overwrite=False)
    variant = prepared.get("variant") if isinstance(prepared.get("variant"), dict) else {}
    if variant.get("owns_actor_numberplate") is not True:
        raise RuntimeError("Numberplate placement reset is only enabled for LiveryLab-owned custom numberplate variants.")
    owned_indices = _numberplate_variant_indices(variant)
    if not owned_indices:
        raise RuntimeError("Skin has no owned numberplate indices.")
    entry, skins, entry_index = _get_my_skin_entry(skin_key)
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    if not source_skin:
        raise RuntimeError("This skin has no source_skin metadata. Recreate the skin with the current Livery Creator first.")
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    game_root = normalize_game_root(root or project.get("gameRoot") or entry.get("game_root") or DEFAULT_ROOT)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    actor_rel = str(files.get("actor") or "").strip()
    source_actor_path = _skin_file_disk_path(game_root, entry, actor_rel)
    if source_actor_path is None or not source_actor_path.exists():
        raise FileNotFoundError("Actor file for this skin was not found")
    local_actor_path = _ensure_local_skin_file_copy(game_root, entry, actor_rel, source=source_actor_path)
    if local_actor_path is None or not local_actor_path.exists():
        raise FileNotFoundError("Local actor file for this skin could not be prepared")
    local_root = _entry_local_root(entry)
    try:
        writes_local_skin_actor = bool(local_root and _path_is_within(local_actor_path.resolve(), local_root.resolve()))
    except Exception:
        writes_local_skin_actor = False
    if not writes_local_skin_actor:
        raise RuntimeError("Reset only writes local LiveryLab skin files.")

    source_actor_rel = str(source_skin.get("actor") or actor_rel).strip()
    base_actor_path = _disk_from_game_rel(game_root, source_actor_rel)
    if base_actor_path is None or not base_actor_path.exists():
        raise FileNotFoundError("Source actor file from source_skin metadata was not found")
    source_selector, selector_source = _source_numberplate_selector(game_root, source_skin)
    if not source_selector:
        raise RuntimeError("Source skin has no usable numberplate selector.")
    placement_sources = variant.get("placement_sources") if isinstance(variant.get("placement_sources"), dict) else {}
    placement_calibrations_by_surface = _numberplate_placement_calibrations_for_actor(local_actor_path)
    synthetic_calibrations_by_surface: dict[int, dict[str, Any]] = {}
    for source_info in placement_sources.values():
        if not isinstance(source_info, dict) or source_info.get("synthetic") is not True:
            continue
        try:
            generated_surface = int(source_info.get("generated_surface") or 0)
        except Exception:
            generated_surface = 0
        calibration = placement_calibrations_by_surface.get(generated_surface)
        if generated_surface and isinstance(calibration, dict) and isinstance(calibration.get("actorBaseline"), dict):
            synthetic_calibrations_by_surface[generated_surface] = calibration

    target_indices: set[int] | None = None
    if isinstance(placement, dict) and placement.get("index") is not None:
        target_indices = {int(placement.get("index"))}

    before_data = local_actor_path.read_bytes()
    after_data, reset_info = _actor_reset_owned_placements_from_source(
        before_data,
        base_actor_path.read_bytes(),
        source_selector,
        owned_indices,
        target_indices=target_indices,
        synthetic_calibrations_by_surface=synthetic_calibrations_by_surface,
    )
    actor_backup = None
    if after_data != before_data:
        actor_backup = safe_write_bytes(local_actor_path, after_data)

    restored_indices = {int(row.get("index")) for row in reset_info.get("restored", []) if str(row.get("index", "")).strip()}
    project_path_text = str(project.get("projectPath") or prepared.get("projectPath") or "").strip()
    project_backup = None
    overlay_info: dict[str, Any] = {"changed": False, "removed": []}
    if project_path_text:
        project_backup, overlay_info = _remove_numberplate_overlay_entries(Path(project_path_text), restored_indices)

    refreshed = prepare_livery_numberplate_overlay(skin_key, root, overwrite=False)
    if after_data != before_data:
        _mark_my_skin_game_sync_required(entry, "Numberplate actor placement restore", "reset_livery_numberplate_actor_placements_from_source")
        skins[entry_index] = entry
        save_my_skins(skins)
    return {
        "actorPath": str(local_actor_path),
        "actorBackup": str(actor_backup) if actor_backup else "",
        "actorChanged": after_data != before_data,
        "projectBackup": str(project_backup) if project_backup else "",
        "overlay": overlay_info,
        "restored": _json_ready(reset_info.get("restored") or []),
        "warnings": reset_info.get("warnings") or [],
        "sourceActor": str(base_actor_path),
        "sourceSelector": _json_ready(source_selector),
        "sourceSelectorSource": selector_source,
        "placements": _json_ready(refreshed.get("placements") or []),
        "allPlacements": _json_ready(refreshed.get("allPlacements") or []),
        "numberplateOverlay": _json_ready(refreshed.get("numberplateOverlay") or {}),
    }


def inspect_livery_numberplate_roundtrip(
    skin_key: str,
    root: str | Path | None,
    placement: dict[str, Any],
    transform: dict[str, Any],
    calibration: dict[str, Any],
) -> dict[str, Any]:
    prepared = prepare_livery_numberplate_overlay(skin_key, root, overwrite=False)
    if not isinstance(placement, dict) or placement.get("index") is None:
        raise ValueError("Numberplate actor placement index is required")

    target_index = int(placement.get("index"))
    current_transform = _coerce_preview_numberplate_transform(transform if isinstance(transform, dict) else {})
    has_calibration = isinstance(calibration, dict) and any(
        str(calibration.get(key, "")).strip() != ""
        for key in ("x", "y", "z", "rx", "ry", "rz", "sx", "sy")
    )
    base_transform = _coerce_preview_numberplate_transform(calibration if isinstance(calibration, dict) else {})
    preview_delta = {
        "x": current_transform["x"] - base_transform["x"],
        "y": current_transform["y"] - base_transform["y"],
        "z": current_transform["z"] - base_transform["z"],
        "rx": current_transform["rx"] - base_transform["rx"],
        "ry": current_transform["ry"] - base_transform["ry"],
        "rz": current_transform["rz"] - base_transform["rz"],
        "sx": current_transform["sx"] - base_transform["sx"],
        "sy": current_transform["sy"] - base_transform["sy"],
    }

    all_placements = prepared.get("allPlacements") if isinstance(prepared.get("allPlacements"), list) else []
    target_placement = None
    for row in all_placements:
        try:
            if int(row.get("index", -1)) == target_index:
                target_placement = row
                break
        except Exception:
            continue
    if not target_placement:
        raise ValueError(f"Actor placement {target_index} was not found")

    target_surface = int(target_placement.get("surface") or target_placement.get("outer_field_number") or 0)
    surface_kind = _numberplate_surface_kind(target_placement)
    calibration_data = _numberplate_apply_calibration_for_placement(
        calibration if isinstance(calibration, dict) else {},
        target_placement,
    )
    source_placement = _find_numberplate_source_placement(all_placements, target_placement)
    base_placement = source_placement or _find_numberplate_base_placement_for_surface(all_placements, target_surface, target_index)

    factors = _numberplate_export_factors(calibration_data, surface_kind)
    actor_delta: dict[str, float] = {}
    actor_target: dict[str, Any] = {}
    warnings: list[str] = []
    supported = False

    if not has_calibration:
        warnings.append("No preview calibration is available for this placement.")
    if not base_placement:
        warnings.append("No base actor placement was found for this surface.")

    if surface_kind in {"door", "hood"}:
        baseline_placement = base_placement or target_placement
        base_position, base_rotation, base_scale, baseline_source = _numberplate_actor_baseline(
            calibration_data,
            baseline_placement,
        )
        required = {
            "actor baseline scale.x": base_scale[0],
            "actor baseline scale.z": base_scale[2],
        }
        if surface_kind == "hood":
            hood_rotation_key, hood_rotation_index, hood_rotation_warning = _numberplate_hood_rotation_axis(calibration_data, base_rotation)
            if hood_rotation_warning:
                warnings.append(hood_rotation_warning)
            required.update(
                {
                    "actor baseline position.z": base_position[2],
                }
            )
            if hood_rotation_key is not None and hood_rotation_index is not None:
                required[f"actor baseline {hood_rotation_key.replace('_', '.')}"] = base_rotation[hood_rotation_index]
        else:
            required.update(
                {
                    "actor baseline position.y": base_position[1],
                    "actor baseline position.z": base_position[2],
                }
            )
        missing = [label for label, value in required.items() if value is None]
        if missing:
            warnings.extend(f"{label} is missing" for label in missing)
        else:
            if surface_kind == "hood":
                hood_rotation_delta, hood_preview_rotation_key = _numberplate_preview_rotation_delta(preview_delta, calibration_data, surface_kind)
                actor_delta = {
                    "position_z": preview_delta["z"] * factors["position_z"],
                    "scale_x": preview_delta["sx"] * factors["scale_x"],
                    "scale_z": preview_delta["sy"] * factors["scale_z"],
                }
                if hood_rotation_key is not None and hood_rotation_index is not None:
                    actor_delta[hood_rotation_key] = hood_rotation_delta * factors[hood_rotation_key]
                target_position = list(base_position)
                if base_position[0] is not None:
                    actor_delta["position_x"] = preview_delta["x"] * factors["position_x"]
                    target_position[0] = float(base_position[0]) + actor_delta["position_x"]
                elif preview_delta["x"]:
                    _append_skipped_actor_axis_warning(warnings, "actor baseline position.x")
                if base_position[1] is not None:
                    actor_delta["position_y"] = preview_delta["y"] * factors["position_y"]
                    target_position[1] = float(base_position[1]) + actor_delta["position_y"]
                elif preview_delta["y"]:
                    _append_skipped_actor_axis_warning(warnings, "actor baseline position.y")
                target_position[2] = float(base_position[2]) + actor_delta["position_z"]
                target_rotation = list(base_rotation)
                if hood_rotation_key is not None and hood_rotation_index is not None:
                    target_rotation[hood_rotation_index] = float(base_rotation[hood_rotation_index]) + actor_delta[hood_rotation_key]
                actor_target = {
                    "position": target_position,
                    "rotation": target_rotation,
                    "scale": [float(base_scale[0]) + actor_delta["scale_x"], base_scale[1], float(base_scale[2]) + actor_delta["scale_z"]],
                    "baselineSource": baseline_source,
                    "previewRotationKey": hood_preview_rotation_key,
                }
            else:
                door_generated_fit = _numberplate_generated_from_fit(calibration_data)
                door_generated_rotation_y = None
                door_rotation_mode = "preview-delta"
                if door_generated_fit:
                    door_generated_rotation_y, door_rotation_mode = _numberplate_generated_door_rotation_y(
                        target_placement,
                        current_transform,
                        base_transform,
                        base_placement,
                        calibration_data,
                        factors["rotation_y"],
                    )
                actor_delta = {
                    "position_y": preview_delta["y"] * factors["position_y"],
                    "position_z": preview_delta["z"] * factors["position_z"],
                    "rotation_y": (float(door_generated_rotation_y) - float(base_rotation[1])) if door_generated_rotation_y is not None and base_rotation[1] is not None else preview_delta["rx"] * factors["rotation_y"],
                    "scale_x": preview_delta["sx"] * factors["scale_x"],
                    "scale_z": preview_delta["sy"] * factors["scale_z"],
                }
                target_rotation = list(base_rotation)
                if base_rotation[1] is None and actor_delta["rotation_y"]:
                    warnings.append("actor baseline rotation.y is missing")
                elif base_rotation[1] is not None:
                    target_rotation[1] = float(door_generated_rotation_y) if door_generated_rotation_y is not None else float(base_rotation[1]) + actor_delta["rotation_y"]
                actor_target = {
                    "position": [base_position[0], float(base_position[1]) + actor_delta["position_y"], float(base_position[2]) + actor_delta["position_z"]],
                    "rotation": target_rotation,
                    "scale": [float(base_scale[0]) + actor_delta["scale_x"], base_scale[1], float(base_scale[2]) + actor_delta["scale_z"]],
                    "baselineSource": baseline_source,
                    "doorRotationMode": door_rotation_mode,
                }
            supported = has_calibration
    else:
        warnings.append("This surface is not supported by preview-to-actor export diagnostics yet.")

    return {
        "skinKey": skin_key,
        "placementIndex": target_index,
        "surface": target_surface,
        "surfaceKind": surface_kind,
        "supported": supported,
        "hasCalibration": has_calibration,
        "currentPreview": _json_ready(current_transform),
        "calibration": _json_ready(calibration_data),
        "previewDelta": _json_ready(preview_delta),
        "exportFactors": _json_ready(factors),
        "actorCurrent": _json_ready(target_placement),
        "actorBase": _json_ready(base_placement or {}),
        "actorDelta": _json_ready(actor_delta),
        "actorTarget": _json_ready(actor_target),
        "variant": _json_ready(prepared.get("variant") or {}),
        "warnings": warnings,
    }


def apply_livery_numberplate_preview_delta_to_actor(
    skin_key: str,
    root: str | Path | None,
    placement: dict[str, Any],
    transform: dict[str, Any],
    calibration: dict[str, Any],
) -> dict[str, Any]:
    prepared = prepare_livery_numberplate_overlay(skin_key, root, overwrite=False)
    if not isinstance(placement, dict) or placement.get("index") is None:
        raise ValueError("Numberplate actor placement index is required")

    actor_path = Path(str(prepared.get("actorPath") or ""))
    if not actor_path.exists():
        raise FileNotFoundError("Actor file for this skin was not found")
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    game_root = normalize_game_root(root or project.get("gameRoot") or DEFAULT_ROOT)
    entry, skins, entry_index = _get_my_skin_entry(skin_key)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    actor_rel = str(files.get("actor") or "").strip()
    local_actor_path = _ensure_local_skin_file_copy(game_root, entry, actor_rel, source=actor_path)
    if local_actor_path and local_actor_path != actor_path:
        prepared = prepare_livery_numberplate_overlay(skin_key, root, overwrite=False)
        actor_path = Path(str(prepared.get("actorPath") or ""))
        if not actor_path.exists():
            raise FileNotFoundError("Local actor file for this skin was not found")
        project = prepared.get("project") if isinstance(prepared.get("project"), dict) else project
    project_folder = Path(str(project.get("projectFolder") or _livery_project_folder_for_entry(entry)))
    try:
        writes_local_project_actor = _path_is_within(actor_path.resolve(), project_folder.resolve())
    except Exception:
        writes_local_project_actor = False
    local_root = _entry_local_root(entry)
    try:
        writes_local_skin_actor = bool(local_root and _path_is_within(actor_path.resolve(), local_root.resolve()))
    except Exception:
        writes_local_skin_actor = False
    if not (writes_local_project_actor or writes_local_skin_actor):
        raise RuntimeError("Apply placement only writes local LiveryLab skin files. Activate in game publishes the result to EVO.")

    target_index = int(placement.get("index"))
    current_transform = _coerce_preview_numberplate_transform(transform if isinstance(transform, dict) else {})
    base_transform = _coerce_preview_numberplate_transform(calibration if isinstance(calibration, dict) else {})
    preview_delta = {
        "x": current_transform["x"] - base_transform["x"],
        "y": current_transform["y"] - base_transform["y"],
        "z": current_transform["z"] - base_transform["z"],
        "rx": current_transform["rx"] - base_transform["rx"],
        "ry": current_transform["ry"] - base_transform["ry"],
        "rz": current_transform["rz"] - base_transform["rz"],
        "sx": current_transform["sx"] - base_transform["sx"],
        "sy": current_transform["sy"] - base_transform["sy"],
    }

    all_placements = prepared.get("allPlacements") if isinstance(prepared.get("allPlacements"), list) else []
    target_placement = None
    for row in all_placements:
        try:
            if int(row.get("index", -1)) == target_index:
                target_placement = row
                break
        except Exception:
            continue
    if not target_placement:
        raise ValueError(f"Actor placement {target_index} was not found")
    target_surface = int(target_placement.get("surface") or target_placement.get("outer_field_number") or 0)
    surface_kind = _numberplate_surface_kind(target_placement)
    if _preview_numberplate_transform_looks_like_actor_raw(target_placement, current_transform):
        raise RuntimeError("Saved preview placement looks like raw actor coordinates. Reload the 3D preview placement and save it again before applying.")
    if surface_kind == "door" and target_surface not in {3, 4}:
        raise RuntimeError("Applying preview placement currently supports left/right door surfaces only.")
    if surface_kind == "hood" and target_surface != 1:
        raise RuntimeError("Applying preview placement currently supports hood surface 1 only.")
    if surface_kind not in {"door", "hood"}:
        raise RuntimeError("Applying preview placement currently supports calibrated hood and door placements only.")
    selected_numberplate_index = int(target_placement.get("numberplate_index") or target_placement.get("field_2") or 0)
    if selected_numberplate_index <= 0:
        raise RuntimeError("Selected placement does not reference a numberplate variant.")

    created_owned_variant: dict[str, Any] = {}
    variant_state = entry.get("numberplate_variant") if isinstance(entry.get("numberplate_variant"), dict) else {}
    if variant_state.get("owns_actor_numberplate") is not True:
        visual_rel = str(files.get("visual_preset") or "").strip()
        if not visual_rel:
            raise FileNotFoundError("Visual preset for this skin was not found")
        visual_path_text = str(prepared.get("visualPath") or "").strip()
        visual_path = Path(visual_path_text) if visual_path_text else None
        source_visual_path = visual_path if visual_path and visual_path.exists() else _skin_file_disk_path(game_root, entry, visual_rel)
        local_visual_path = _ensure_local_skin_file_copy(game_root, entry, visual_rel, source=source_visual_path)
        if local_visual_path is None or not local_visual_path.exists():
            raise FileNotFoundError("Local visual preset for this skin could not be prepared")

        writer_root = local_root or actor_path.parent
        writer = GameFileWriter(writer_root, "create local numberplate placement variant", [actor_path, local_visual_path])
        actor_data = actor_path.read_bytes()
        missing_before = validate_actor_numberplate_resources(actor_path, actor_data)
        if missing_before:
            raise RuntimeError(
                "Actor contains missing numberplate resources. Repair or remove these references before saving a numberplate variant:\n"
                + "\n".join(f"- {row}" for row in missing_before)
            )
        selector = extract_visual_numberplate_selector(local_visual_path.read_bytes())
        new_actor_data, new_variant = append_actor_numberplate_variant_from_selected_data(actor_data, selector=selector)
        missing_after = validate_actor_numberplate_resources(actor_path, new_actor_data)
        if missing_after:
            raise RuntimeError(
                "Numberplate variant would reference missing resources:\n"
                + "\n".join(f"- {row}" for row in missing_after)
            )
        actor_backup = _write_game_bytes(actor_path, new_actor_data, writer, label="local actor numberplate placement variant")
        visual_changed, visual_backup = update_visual_numberplate_selector_file(local_visual_path, new_variant["selector"], writer=writer)
        inspected_variant = inspect_visual_numberplate_variant(actor_path, local_visual_path)
        inspected_variant["updated_existing"] = False
        inspected_variant["created_by"] = "LiveryLab"
        inspected_variant["owns_actor_numberplate"] = True
        if isinstance(new_variant.get("placement_sources"), dict):
            inspected_variant["placement_sources"] = _json_ready(new_variant.get("placement_sources"))
        entry["numberplate_variant"] = _json_ready(inspected_variant)
        skins[entry_index] = entry
        save_my_skins(skins)
        created_owned_variant = {
            "actorChanged": True,
            "actorBackup": str(actor_backup) if actor_backup else "",
            "visualChanged": bool(visual_changed),
            "visualBackup": str(visual_backup) if visual_backup else "",
            "variant": _json_ready(inspected_variant),
        }

        prepared = prepare_livery_numberplate_overlay(skin_key, root, overwrite=False)
        actor_path = Path(str(prepared.get("actorPath") or ""))
        if not actor_path.exists():
            raise FileNotFoundError("Local actor file for this skin was not found")
        project = prepared.get("project") if isinstance(prepared.get("project"), dict) else project
        all_placements = prepared.get("allPlacements") if isinstance(prepared.get("allPlacements"), list) else []
        owned_numberplate_index_1based = int(inspected_variant.get("numberplate_index_1based") or 0)
        target_placement = None
        for row in prepared.get("placements", []) if isinstance(prepared.get("placements"), list) else []:
            try:
                if int(row.get("surface") or row.get("outer_field_number") or 0) == target_surface:
                    target_placement = row
                    break
            except Exception:
                continue
        if target_placement is None and owned_numberplate_index_1based > 0:
            for row in all_placements:
                try:
                    row_surface = int(row.get("surface") or row.get("outer_field_number") or 0)
                    row_numberplate = int(row.get("numberplate_index") or row.get("field_2") or 0)
                except Exception:
                    continue
                if row_surface == target_surface and row_numberplate == owned_numberplate_index_1based:
                    target_placement = row
                    break
        if target_placement is None:
            raise RuntimeError("Could not find the local owned numberplate placement for this surface")
        target_index = int(target_placement.get("index"))
        selected_numberplate_index = int(target_placement.get("numberplate_index") or target_placement.get("field_2") or 0)

    calibration_data = _numberplate_apply_calibration_for_placement(
        calibration if isinstance(calibration, dict) else {},
        target_placement,
    )
    factors = _numberplate_export_factors(calibration_data, surface_kind)
    base_placement = _find_numberplate_base_placement_for_surface(all_placements, target_surface, target_index)
    source_placement = _find_numberplate_source_placement(all_placements, target_placement)
    if source_placement:
        base_placement = source_placement
    if not base_placement:
        raise RuntimeError("Could not find base numberplate placement for this surface")
    base_actor_index = int(base_placement.get("index"))

    baseline_placement = base_placement or target_placement
    base_position, _base_rotation, base_scale, _baseline_source = _numberplate_actor_baseline(
        calibration_data,
        baseline_placement,
    )
    required = {
        "actor baseline scale.x": base_scale[0],
        "actor baseline scale.z": base_scale[2],
    }
    warnings: list[str] = []
    hood_rotation_key, hood_rotation_index, hood_rotation_warning = _numberplate_hood_rotation_axis(calibration_data, _base_rotation)
    if hood_rotation_warning:
        warnings.append(hood_rotation_warning)
    if surface_kind == "hood":
        required.update(
            {
                "actor baseline position.z": base_position[2],
            }
        )
        if hood_rotation_key is not None and hood_rotation_index is not None:
            required[f"actor baseline {hood_rotation_key.replace('_', '.')}"] = _base_rotation[hood_rotation_index]
    else:
        required.update(
            {
                "actor baseline position.y": base_position[1],
                "actor baseline position.z": base_position[2],
                "actor baseline rotation.y": _base_rotation[1],
            }
        )
    for key, value in required.items():
        if value is None:
            raise RuntimeError(f"{key} is missing; apply placement will not create missing actor fields.")

    target_position = list(base_position)
    target_scale = list(base_scale)
    door_rotation_mode = ""
    if surface_kind == "hood":
        if base_position[0] is not None:
            target_position[0] = float(base_position[0]) + preview_delta["x"] * factors["position_x"]
        elif preview_delta["x"]:
            _append_skipped_actor_axis_warning(warnings, "actor baseline position.x")
        if base_position[1] is not None:
            target_position[1] = float(base_position[1]) + preview_delta["y"] * factors["position_y"]
        elif preview_delta["y"]:
            _append_skipped_actor_axis_warning(warnings, "actor baseline position.y")
    target_rotation = list(_base_rotation)
    if surface_kind == "hood":
        pass
    elif _numberplate_generated_from_fit(calibration_data) and _base_rotation[1] is not None:
        target_rotation[1], door_rotation_mode = _numberplate_generated_door_rotation_y(
            target_placement,
            current_transform,
            base_transform,
            base_placement,
            calibration_data,
            factors["rotation_y"],
        )
    elif _base_rotation[1] is not None:
        door_rotation_mode = "preview-delta"
        target_rotation[1] = float(_base_rotation[1]) + preview_delta["rx"] * factors["rotation_y"]
    elif preview_delta["rx"]:
        raise RuntimeError("actor baseline rotation.y is missing; door rotation cannot be applied.")
    if surface_kind != "hood":
        target_position[1] = float(base_position[1]) + preview_delta["y"] * factors["position_y"]
    target_position[2] = float(base_position[2]) + preview_delta["z"] * factors["position_z"]
    target_scale[0] = float(base_scale[0]) + preview_delta["sx"] * factors["scale_x"]
    target_scale[2] = float(base_scale[2]) + preview_delta["sy"] * factors["scale_z"]
    hood_preview_rotation_key = ""
    hood_rotation_delta = 0.0
    if surface_kind == "hood":
        hood_rotation_delta, hood_preview_rotation_key = _numberplate_preview_rotation_delta(preview_delta, calibration_data, surface_kind)
    actor_delta = {
        "position_y": preview_delta["y"] * factors["position_y"],
        "position_z": preview_delta["z"] * factors["position_z"],
        "scale_x": preview_delta["sx"] * factors["scale_x"],
        "scale_z": preview_delta["sy"] * factors["scale_z"],
    }
    if surface_kind == "hood":
        actor_delta.pop("position_y", None)
        if base_position[0] is not None:
            actor_delta["position_x"] = preview_delta["x"] * factors["position_x"]
        if base_position[1] is not None:
            actor_delta["position_y"] = preview_delta["y"] * factors["position_y"]
        if hood_rotation_key is not None and hood_rotation_index is not None:
            actor_delta[hood_rotation_key] = hood_rotation_delta * factors[hood_rotation_key]
            target_rotation[hood_rotation_index] = float(_base_rotation[hood_rotation_index]) + actor_delta[hood_rotation_key]
    else:
        actor_delta["rotation_y"] = (float(target_rotation[1]) - float(_base_rotation[1])) if _base_rotation[1] is not None else preview_delta["rx"] * factors["rotation_y"]

    actor_data = actor_path.read_bytes()
    actor_fields, payload_index, payload_fields = _actor_payload_fields_for_update(actor_data)
    changed = False
    placement_index = 0
    new_payload_fields: list[ProtoField] = []
    for field in payload_fields:
        if field.number != 52 or field.wire != 2:
            new_payload_fields.append(field)
            continue
        placement_fields = parse_fields(field.value)
        source_raw: bytes | None = None
        placement_index = 0
        for placement_field in placement_fields:
            if placement_field.wire != 2:
                continue
            if placement_index == base_actor_index:
                source_raw = bytes(placement_field.value)
                break
            placement_index += 1
        if source_raw is None:
            raise RuntimeError("Could not find base actor placement bytes for this surface")

        new_placement_fields: list[ProtoField] = []
        placement_index = 0
        for placement_field in placement_fields:
            if placement_field.wire == 2 and placement_index == target_index:
                updated_value = _actor_clone_placement_entry(source_raw, selected_numberplate_index)
                rotation_payload = [None, target_rotation[1], None]
                if surface_kind == "hood":
                    rotation_payload = [None, None, None]
                    actor_baseline = calibration_data.get("actorBaseline") if isinstance(calibration_data.get("actorBaseline"), dict) else {}
                    for rotation_axis, rotation_key in enumerate(("rotation_x", "rotation_y", "rotation_z")):
                        if actor_baseline.get(rotation_key) not in (None, "") and target_rotation[rotation_axis] is not None:
                            rotation_payload[rotation_axis] = target_rotation[rotation_axis]
                    if hood_rotation_index in {0, 1, 2}:
                        rotation_payload[int(hood_rotation_index)] = target_rotation[int(hood_rotation_index)]
                updated_value = _actor_update_placement_entry_bytes(
                    updated_value,
                    {
                        "position": [target_position[0] if surface_kind == "hood" else None, target_position[1], target_position[2]],
                        "rotation": rotation_payload,
                        "scale": [target_scale[0], None, target_scale[2]],
                    },
                )
                if updated_value != bytes(placement_field.value):
                    changed = True
                    new_placement_fields.append(ProtoField(placement_field.number, placement_field.wire, updated_value))
                else:
                    new_placement_fields.append(placement_field)
                placement_index += 1
                continue
            new_placement_fields.append(placement_field)
            if placement_field.wire == 2:
                placement_index += 1
        new_payload_fields.append(ProtoField(field.number, field.wire, build_fields(new_placement_fields)))

    actor_backup = None
    if changed:
        new_actor_fields = list(actor_fields)
        new_actor_fields[payload_index] = ProtoField(103, 2, build_fields(new_payload_fields))
        actor_backup = safe_write_bytes(actor_path, build_fields(new_actor_fields))
        _mark_my_skin_game_sync_required(entry, "Numberplate placement changes", "apply_livery_numberplate_preview_delta_to_actor")
        skins[entry_index] = entry
        save_my_skins(skins)
    elif created_owned_variant:
        _mark_my_skin_game_sync_required(entry, "Numberplate placement variant created", "apply_livery_numberplate_preview_delta_to_actor")
        skins[entry_index] = entry
        save_my_skins(skins)

    refreshed = prepare_livery_numberplate_overlay(skin_key, root, overwrite=False)
    updated_placement = None
    for row in refreshed.get("allPlacements", []) if isinstance(refreshed.get("allPlacements"), list) else []:
        try:
            if int(row.get("index", -1)) == target_index:
                updated_placement = row
                break
        except Exception:
            continue
    return {
        "actorPath": str(actor_path),
        "actorBackup": str(actor_backup) if actor_backup else "",
        "actorChanged": changed,
        "placementIndex": target_index,
        "surfaceKind": surface_kind,
        "previewDelta": _json_ready(preview_delta),
        "factors": factors,
        "actorDelta": _json_ready(actor_delta),
        "actorTarget": _json_ready(
            {
                "position": target_position,
                "rotation": target_rotation,
                "scale": target_scale,
            }
        ),
        "calibrationDebug": _json_ready(
            {
                "generatedFromFit": _numberplate_generated_from_fit(calibration_data),
                "generatedFitSourcePlacementIndex": calibration_data.get("generatedFitSourcePlacementIndex"),
                "doorRotationMode": door_rotation_mode,
                "hoodRotationKey": hood_rotation_key if surface_kind == "hood" else "",
                "hoodPreviewRotationKey": hood_preview_rotation_key,
                "actorBaseline": calibration_data.get("actorBaseline") if isinstance(calibration_data.get("actorBaseline"), dict) else {},
                "baseTransform": base_transform,
                "currentTransform": current_transform,
            }
        ),
        "warnings": warnings,
        "before": _json_ready(target_placement),
        "base": _json_ready(base_placement),
        "after": _json_ready(updated_placement or {}),
        "placements": _json_ready(refreshed.get("placements") or []),
        "allPlacements": _json_ready(refreshed.get("allPlacements") or []),
        "createdOwnedVariant": _json_ready(created_owned_variant),
    }


def apply_livery_numberplate_door_preview_delta_to_actor(
    skin_key: str,
    root: str | Path | None,
    placement: dict[str, Any],
    transform: dict[str, Any],
    calibration: dict[str, Any],
) -> dict[str, Any]:
    return apply_livery_numberplate_preview_delta_to_actor(skin_key, root, placement, transform, calibration)


def _coerce_preview_numberplate_text_layer(text_layer: dict[str, Any]) -> dict[str, float]:
    defaults = {"x": 0.0, "y": 0.0, "scale": 0.3, "spacing": 0.02}
    values: dict[str, float] = {}
    for key, fallback in defaults.items():
        try:
            value = float(text_layer.get(key, fallback))
        except Exception:
            value = fallback
        if key == "scale":
            value = max(0.05, min(1.0, value))
        elif key == "spacing":
            value = max(-0.1, min(0.5, value))
        else:
            value = max(-1.0, min(1.0, value))
        values[key] = value
    return values


def save_livery_numberplate_placement(
    skin_key: str,
    root: str | Path | None,
    placement: dict[str, Any],
    transform: dict[str, Any],
    text: str = "",
    text_layer: dict[str, Any] | None = None,
    write_actor: bool = True,
) -> dict[str, Any]:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    _assert_my_skin_policy(entry, "canUseEditablePreviewTools")
    if write_actor:
        raise RuntimeError("Direct actor writes from 3D preview coordinates are disabled. Save the preview calibration instead.")
    prepared = prepare_livery_numberplate_overlay(skin_key, root, overwrite=False)
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    project_path_text = str(project.get("projectPath") or prepared.get("projectPath") or "").strip()
    if not project_path_text:
        raise ValueError("LiveryProjects preview_project.json path is missing")
    project_path = Path(project_path_text)
    if not isinstance(placement, dict) or placement.get("index") is None:
        raise ValueError("Numberplate actor placement index is required")

    actor_index = int(placement.get("index"))
    all_placements = prepared.get("allPlacements") if isinstance(prepared.get("allPlacements"), list) else []
    actor_placement = None
    for row in all_placements:
        try:
            row_index = int(row.get("index", -1))
        except Exception:
            row_index = -1
        if row_index == actor_index:
            actor_placement = row
            break
    if not actor_placement:
        raise ValueError(f"Actor placement {actor_index} was not found")

    coerced_transform = _coerce_preview_numberplate_transform(transform if isinstance(transform, dict) else {})
    if _preview_numberplate_transform_looks_like_actor_raw(actor_placement, coerced_transform):
        raise RuntimeError("Preview placement looks like raw actor coordinates. Reload the 3D preview placement and save it again.")
    coerced_text_layer = _coerce_preview_numberplate_text_layer(text_layer if isinstance(text_layer, dict) else {})
    preview_text = re.sub(r"\D+", "", str(text or "").strip())[:3]
    save_key = _numberplate_placement_save_key(actor_placement, actor_index)
    now = datetime.now().isoformat(timespec="seconds")
    existing: dict[str, Any] = {}
    if project_path.exists():
        try:
            data = json.loads(project_path.read_text(encoding="utf-8"))
            existing = data if isinstance(data, dict) else {}
        except Exception:
            existing = {}
    else:
        existing = dict(project)

    overlay = existing.get("numberplateOverlay") if isinstance(existing.get("numberplateOverlay"), dict) else {}
    placement_map = overlay.get("placements") if isinstance(overlay.get("placements"), dict) else {}
    placement_map[save_key] = {
        "placement": actor_placement.get("label") or placement.get("label") or "",
        "actorIndex": actor_index,
        "surface": actor_placement.get("surface"),
        "numberplateIndex": actor_placement.get("numberplate_index"),
        "transform": coerced_transform,
        "text": preview_text,
        "textLayer": coerced_text_layer,
        "savedAt": now,
    }
    overlay = {
        **overlay,
        "version": 1,
        "updatedAt": now,
        "placements": placement_map,
    }
    existing["numberplateOverlay"] = overlay
    existing["updatedAt"] = now
    project_backup = safe_write_text(project_path, json.dumps(_json_ready(existing), indent=2, ensure_ascii=False))

    actor_changed = False
    actor_backup: Path | None = None
    actor_update: dict[str, Any] | None = None
    if write_actor:
        actor_path = Path(str(prepared.get("actorPath") or ""))
        if not actor_path.exists():
            raise FileNotFoundError("Actor file for this skin was not found")
        actor_update = _actor_placement_from_preview_transform(actor_placement, coerced_transform)
        actor_changed, actor_backup = update_actor_numberplate_file(actor_path, placements=[actor_update])

    return {
        "projectPath": str(project_path),
        "projectBackup": str(project_backup) if project_backup else "",
        "saveKey": save_key,
        "numberplateOverlay": _json_ready(overlay),
        "actorChanged": actor_changed,
        "actorBackup": str(actor_backup) if actor_backup else "",
        "actorUpdate": _json_ready(actor_update or {}),
    }


def update_game_textures_from_livery_project(skin_key: str, root: str | Path | None = None, keep_dds: bool = False) -> dict[str, Any]:
    prepared = prepare_livery_preview_project(skin_key, root)
    project = prepared.get("project") if isinstance(prepared.get("project"), dict) else {}
    game_root = normalize_game_root(root or project.get("gameRoot") or DEFAULT_ROOT)
    key = str(skin_key or "").strip().lower()
    skin = next((row for row in list_my_skins(game_root) if str(row.get("key", "")).lower() == key), None)
    if not skin:
        raise KeyError(f"My Skin not found: {skin_key}")
    files = dict(skin.get("files", {}) or {})
    skin_folder = _disk_from_game_rel(game_root, str(files.get("skin_folder") or ""))
    if not skin_folder or not skin_folder.exists():
        raise FileNotFoundError(f"Skin folder not found: {files.get('skin_folder') or '-'}")
    writer = GameFileWriter(
        game_root,
        "update game textures from livery project",
        [skin_folder],
        backups_enabled=False,
    )

    specs = _livery_project_texture_specs(game_root, skin, project, skin_folder, local_only=False, include_banner=False)
    texconv = find_texconv_executable()
    if texconv is None:
        raise RuntimeError("texconv.exe was not found. Place it in LiveryLab/tools or configure it in Settings.")

    tmp_dir = create_short_work_temp_dir("gu")
    results: list[dict[str, Any]] = []
    try:
        for index, spec in enumerate(specs, start=1):
            label = str(spec["label"])
            png = Path(str(spec.get("png") or ""))
            texture = (
                _find_banner_texture_for_skin(game_root, skin, skin_folder)
                if spec.get("banner")
                else Path(str(spec.get("texture") or "")) if str(spec.get("texture") or "").strip() else _find_texture_by_stem(skin_folder, str(spec["stem"]))
            )
            item: dict[str, Any] = {"label": label, "png": str(png), "texture": str(texture or ""), "status": "pending"}
            if not png.exists():
                item.update({"status": "skipped" if spec.get("optional") else "missing_png", "reason": "optional project PNG not found" if spec.get("optional") else "project PNG not found"})
                results.append(item)
                continue
            if texture is None or not texture.exists():
                item.update({"status": "skipped" if spec.get("optional") else "missing_texture", "reason": "optional installed .texture not found" if spec.get("optional") else "installed .texture not found"})
                results.append(item)
                continue
            try:
                texture.resolve().relative_to(skin_folder.resolve())
            except ValueError:
                item.update({"status": "skipped", "reason": "target texture is outside the selected skin folder"})
                results.append(item)
                continue
            mips = texture.with_suffix(".texturemips")
            if not mips.exists():
                item.update({"status": "skipped" if spec.get("optional") else "missing_mips", "reason": "optional installed .texturemips not found" if spec.get("optional") else "installed .texturemips not found"})
                results.append(item)
                continue

            desc = parse_texture_descriptor(texture)
            tiled = mips.read_bytes()
            format_name, auto_note = choose_auto_format(texture, desc, len(tiled), str(spec.get("slot") or ""))
            dds_path = png.with_suffix(".dds") if keep_dds else tmp_dir / f"g{index}.dds"
            repair = _repair_neutral_project_png_size(png, texture, str(spec.get("stem") or ""))
            if repair.get("repaired"):
                item["sizeRepair"] = repair
            validate_project_png_size_for_texture(png, desc, label)
            convert_image_to_dds_with_texconv(png, dds_path, format_name, texconv, int(desc["mips"]))
            texturemips_data = texturemips_bytes_from_dds(texture, dds_path)
            write_result = writer.write_bytes(mips, texturemips_data, label=label, backup=True)
            item.update({
                "status": "updated",
                "texturemips": str(mips),
                "dds": str(dds_path) if keep_dds else "",
                "format": format_name,
                "mips": int(desc["mips"]),
                "width": int(desc["width"]),
                "height": int(desc["height"]),
                "note": auto_note,
                "message": f".texturemips geschrieben: {mips}\nBytes: {len(texturemips_data):,}\nBackup: {write_result.backup or '-'}",
                "writeResult": write_result.to_dict(),
            })
            results.append(item)
        ext_banner_item = _ensure_ext_banner_project_texture_for_skin(
            skin,
            game_root,
            game_root,
            skin_folder,
            project,
            writer=writer,
            only_if_newer=False,
        )
        results.append(ext_banner_item)
    finally:
        cleanup_short_work_temp_dir(tmp_dir)

    visual_rel = normalize_skin_asset_rel_path(str(files.get("visual_preset") or ""))
    ext_banner_sync = _ensure_ext_banner_for_game_entry(skin, game_root, visual_rel, [], writer)
    updated = sum(1 for item in results if item.get("status") == "updated")
    return {
        **prepared,
        "updated": updated,
        "skipped": sum(1 for item in results if item.get("status") == "skipped"),
        "missing": sum(1 for item in results if str(item.get("status", "")).startswith("missing")),
        "keepDds": bool(keep_dds),
        "items": results,
        "extBannerSync": ext_banner_sync,
        "writer": writer.to_dict(),
    }


def start_livery_preview_watcher(path: str | Path, allowed_names: list[str] | None = None) -> dict[str, Any]:
    return LIVERY_PREVIEW_WATCHER.start(path, allowed_names=allowed_names)


def stop_livery_preview_watcher() -> dict[str, Any]:
    return LIVERY_PREVIEW_WATCHER.stop()


def get_livery_preview_watcher_status() -> dict[str, Any]:
    return LIVERY_PREVIEW_WATCHER.get_status()


def _my_skin_thumbnail_watch_path(skin_key: str) -> Path:
    entry, _skins, _index = _get_my_skin_entry(skin_key)
    return _project_skin_thumbnail_path_for_entry(entry)


def start_my_skin_thumbnail_watcher(skin_key: str) -> dict[str, Any]:
    path = _my_skin_thumbnail_watch_path(skin_key)
    status = MY_SKIN_THUMBNAIL_WATCHER.start(path)
    status["skin_key"] = str(skin_key or "")
    status["path"] = str(path)
    return status


def stop_my_skin_thumbnail_watcher() -> dict[str, Any]:
    return MY_SKIN_THUMBNAIL_WATCHER.stop()


def get_my_skin_thumbnail_watcher_status() -> dict[str, Any]:
    return MY_SKIN_THUMBNAIL_WATCHER.get_status()


def start_driver_preview_watcher(path: str | Path, allowed_names: list[str] | None = None) -> dict[str, Any]:
    return DRIVER_PREVIEW_WATCHER.start(path, allowed_names=allowed_names)


def stop_driver_preview_watcher() -> dict[str, Any]:
    return DRIVER_PREVIEW_WATCHER.stop()


def get_driver_preview_watcher_status() -> dict[str, Any]:
    return DRIVER_PREVIEW_WATCHER.get_status()


def open_path_in_explorer(path: str | Path) -> str:
    target = Path(path).expanduser()
    if not target.exists():
        raise FileNotFoundError(f"Folder does not exist: {target}")
    if target.is_file():
        target = target.parent
    if sys.platform.startswith("win"):
        os.startfile(str(target))  # type: ignore[attr-defined]
    elif sys.platform == "darwin":
        subprocess.Popen(["open", str(target)])
    else:
        subprocess.Popen(["xdg-open", str(target)])
    return str(target)


def backup_file(path: Path) -> Path | None:
    if not path.exists() or not path.is_file():
        return None
    if is_app_managed_storage_path(path):
        return None
    backup_dir = path.parent / "_acevo_backups"
    backup_dir.mkdir(parents=True, exist_ok=True)
    stamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
    backup = backup_dir / f"{path.name}.{stamp}.bak"
    shutil.copy2(path, backup)
    return backup


def safe_write_bytes(path: Path, data: bytes) -> Path | None:
    path.parent.mkdir(parents=True, exist_ok=True)
    backup = backup_file(path) if path.exists() else None
    path.write_bytes(data)
    return backup


def safe_write_text(path: Path, text: str) -> Path | None:
    path.parent.mkdir(parents=True, exist_ok=True)
    backup = backup_file(path) if path.exists() else None
    path.write_text(text, encoding="utf-8")
    return backup


def _xor_key_bytes(key: int = PACK_KEY) -> bytes:
    return int(key).to_bytes(8, "little", signed=False)


def xor_pack_data(data: bytearray, key: int = PACK_KEY, start_offset: int = 0) -> None:
    kspkg_extract.xor_in_place(data, key, start_offset)


def fnv1a64_hash(bytes_data: bytes) -> int:
    hash_value = 14695981039346656037
    prime = 0x100000001B3
    for value in bytes_data:
        hash_value ^= value
        hash_value = (hash_value * prime) & 0xFFFFFFFFFFFFFFFF
    return hash_value


def pack_hash_path(path: str) -> int:
    text = str(path or "").lower().replace("/", "\\")
    return fnv1a64_hash(text.encode("utf-16le"))


def _parse_pack_table(table: bytearray, pack_size: int) -> list[PackPackageEntry]:
    xor_pack_data(table, PACK_KEY, 0)
    entries: list[PackPackageEntry] = []
    for index in range(PACK_MAX_FILE_COUNT):
        offset = index * PACK_ENTRY_SIZE
        chunk = table[offset : offset + PACK_ENTRY_SIZE]
        if len(chunk) < PACK_ENTRY_SIZE:
            break
        file_name_buffer, unk_always_0, flags, file_name_length, path_hash, file_size, file_offset = PACK_ENTRY_STRUCT.unpack(chunk)
        if path_hash == 0:
            break
        if file_name_length <= 0 or file_name_length > len(file_name_buffer):
            raise ValueError("Invalid pack file table entry name length")
        length = int(file_name_length)
        name = file_name_buffer[:length].decode("ascii", errors="strict")
        if not name or any(ord(char) < 32 for char in name):
            raise ValueError("Invalid pack file table entry name")
        is_directory = bool(flags & 0x0001)
        if not is_directory and (file_size < 0 or file_offset < 0 or file_offset + file_size > pack_size):
            raise ValueError("Invalid pack file table entry offset")
        entries.append(
            PackPackageEntry(
                name=name,
                unk_always_0=int(unk_always_0),
                flags=int(flags),
                file_name_length=int(file_name_length),
                path_hash=int(path_hash),
                file_size=int(file_size),
                file_offset=int(file_offset),
            )
        )
    if not entries:
        raise ValueError("Pack file table is empty")
    return entries


def read_pack_entries(pack_path: str | Path) -> list[PackPackageEntry]:
    path = Path(pack_path)
    size = path.stat().st_size
    if size < PACK_FILE_TABLE_SIZE:
        raise ValueError(f"Pack file is smaller than expected table size: {path}")
    errors: list[str] = []
    with path.open("rb") as stream:
        for trailer_offset in PACK_TABLE_TRAILER_OFFSETS:
            if size < trailer_offset:
                continue
            stream.seek(size - trailer_offset)
            table = bytearray(stream.read(PACK_FILE_TABLE_SIZE))
            try:
                return _parse_pack_table(table, size)
            except ValueError as exc:
                errors.append(f"size-{trailer_offset:#x}: {exc}")
    detail = "; ".join(errors) if errors else "no table candidate could be read"
    raise ValueError(f"Could not read pack file table: {path} ({detail})")


def inspect_pack_file(pack_path: str | Path, preview_limit: int = 400) -> dict[str, Any]:
    path = Path(pack_path)
    entries = read_pack_entries(path)
    files = [entry for entry in entries if not entry.is_directory]
    directories = [entry for entry in entries if entry.is_directory]
    encrypted = [entry for entry in files if entry.is_encrypted]
    sorted_names = sorted((entry.name for entry in entries), key=str.lower)
    preview_names = sorted_names[: max(0, int(preview_limit))]
    lines = [
        f"Package: {path.name}",
        f"Size: {path.stat().st_size:,} bytes",
        f"Entries: {len(entries)}",
        f"Files: {len(files)}",
        f"Directories: {len(directories)}",
        f"Encrypted files: {len(encrypted)}",
        "",
        "Entries:",
    ]
    lines.extend(f"  - {name}" for name in preview_names)
    if len(sorted_names) > len(preview_names):
        lines.append("")
        lines.append(f"... {len(sorted_names) - len(preview_names)} more entries omitted")
    return {
        "file": str(path),
        "size_bytes": path.stat().st_size,
        "entry_count": len(entries),
        "file_count": len(files),
        "directory_count": len(directories),
        "encrypted_count": len(encrypted),
        "entries_preview": preview_names,
        "summary": "\n".join(lines),
    }


def write_pack_file_listing(pack_path: str | Path, out_path: str | Path) -> dict[str, Any]:
    path = Path(pack_path)
    output = Path(out_path)
    entries = read_pack_entries(path)
    output.parent.mkdir(parents=True, exist_ok=True)
    lines = []
    for entry in sorted(entries, key=lambda row: row.name.lower()):
        flags: list[str] = []
        if entry.is_directory:
            flags.append("DIR")
        if entry.is_encrypted:
            flags.append("ENC")
        flag_text = ",".join(flags) if flags else "-"
        lines.append(
            f"{entry.name} | offset:{entry.file_offset:08X} | size:{entry.file_size:08X} | hash:{entry.path_hash:016X} | flags:{flag_text}"
        )
    output.write_text("\n".join(lines) + ("\n" if lines else ""), encoding="utf-8")
    return {
        "file": str(path),
        "output_path": str(output),
        "entry_count": len(entries),
        "summary": "\n".join(
            [
                f"Package list written: {path.name}",
                f"Output: {output}",
                f"Entries: {len(entries)}",
            ]
        ),
    }


def parse_pack_selection_lines(selection_text: str) -> list[str]:
    values: list[str] = []
    seen: set[str] = set()
    for raw_line in str(selection_text or "").splitlines():
        line = norm_slashes(raw_line.strip())
        if not line:
            continue
        key = line.lower().rstrip("\\")
        if key in seen:
            continue
        seen.add(key)
        values.append(line)
    return values


def filter_pack_entries(entries: list[PackPackageEntry], selectors: list[str]) -> list[PackPackageEntry]:
    if not selectors:
        return list(entries)
    normalized = [norm_slashes(value).rstrip("\\") for value in selectors if str(value).strip()]
    matched: list[PackPackageEntry] = []
    for entry in entries:
        entry_name = norm_slashes(entry.name)
        entry_lower = entry_name.lower()
        keep = False
        for selector in normalized:
            selector_lower = selector.lower()
            if entry_lower == selector_lower or entry_lower.startswith(selector_lower + "\\"):
                keep = True
                break
        if keep:
            matched.append(entry)
    return matched


class PackageExtractionCancelled(RuntimeError):
    pass


def _extract_pack_entries(
    path: Path,
    out_dir: Path,
    entries: list[PackPackageEntry],
    progress_cb: Any | None = None,
    cancel_cb: Any | None = None,
) -> dict[str, Any]:
    out_dir.mkdir(parents=True, exist_ok=True)
    written_files = 0
    written_dirs = 0
    encrypted_files = 0
    total_entries = len(entries)
    total_bytes = sum(max(0, int(entry.file_size)) for entry in entries if not entry.is_directory)
    processed_bytes = 0
    started_at = time.perf_counter()
    last_progress_at = 0.0
    last_percent = -1.0
    created_directories: set[Path] = set()

    def check_cancel() -> None:
        if cancel_cb and cancel_cb():
            raise PackageExtractionCancelled("Package extraction cancelled")

    def ensure_directory(directory: Path) -> None:
        if directory in created_directories:
            return
        directory.mkdir(parents=True, exist_ok=True)
        created_directories.add(directory)

    def emit_progress(
        index: int,
        entry: PackPackageEntry,
        current_file_bytes: int = 0,
        *,
        force: bool = False,
    ) -> None:
        nonlocal last_progress_at, last_percent
        if not progress_cb:
            return
        now = time.perf_counter()
        payload = kspkg_extract.progress_payload(
            current=index,
            total=total_entries,
            path=entry.name,
            files_written=written_files,
            directories_created=written_dirs,
            bytes_processed=processed_bytes,
            total_bytes=total_bytes,
            current_file_bytes=current_file_bytes,
            current_file_size=0 if entry.is_directory else int(entry.file_size),
            elapsed_seconds=now - started_at,
        )
        percent = float(payload.get("percent") or 0.0)
        if percent >= 100.0 and index < total_entries:
            percent = 99.9
            payload["percent"] = percent
        if not force and percent == last_percent and now - last_progress_at < PACK_PROGRESS_INTERVAL_SECONDS:
            return
        if not force and now - last_progress_at < PACK_PROGRESS_INTERVAL_SECONDS:
            return
        last_progress_at = now
        last_percent = percent
        progress_cb(payload)

    with path.open("rb") as stream:
        for index, entry in enumerate(entries, start=1):
            check_cancel()
            rel_parts = split_content_path(entry.name)
            target = out_dir / Path(*rel_parts)
            if entry.is_directory:
                ensure_directory(target)
                written_dirs += 1
                emit_progress(index, entry)
            else:
                ensure_directory(target.parent)
                if entry.file_size <= 0:
                    target.write_bytes(b"")
                    written_files += 1
                    emit_progress(index, entry)
                else:
                    stream.seek(entry.file_offset)
                    remaining = int(entry.file_size)
                    position = 0
                    with target.open("wb") as out_stream:
                        while remaining > 0:
                            check_cancel()
                            chunk = stream.read(min(PACK_EXTRACT_CHUNK_SIZE, remaining))
                            if not chunk:
                                raise EOFError(f"Unexpected end of pack while reading: {entry.name}")
                            if entry.is_encrypted:
                                decrypted_chunk = bytearray(chunk)
                                xor_pack_data(decrypted_chunk, PACK_KEY, position)
                                chunk = decrypted_chunk
                            out_stream.write(chunk)
                            position += len(chunk)
                            remaining -= len(chunk)
                            processed_bytes += len(chunk)
                            emit_progress(index, entry, position)
                    written_files += 1
                    if entry.is_encrypted:
                        encrypted_files += 1
                    emit_progress(index, entry, position, force=index == total_entries)
        if progress_cb and entries and last_percent < 100.0:
            emit_progress(total_entries, entries[-1], 0 if entries[-1].is_directory else int(entries[-1].file_size), force=True)
    elapsed_seconds = max(0.0, time.perf_counter() - started_at)
    bytes_per_second = processed_bytes / elapsed_seconds if processed_bytes and elapsed_seconds > 0 else 0.0
    return {
        "file": str(path),
        "output_dir": str(out_dir),
        "entry_count": len(entries),
        "file_count": written_files,
        "directory_count": written_dirs,
        "encrypted_count": encrypted_files,
        "bytes_processed": processed_bytes,
        "elapsed_seconds": round(elapsed_seconds, 2),
        "bytes_per_second": round(bytes_per_second),
        "summary": "\n".join(
            [
                f"Package extracted: {path.name}",
                f"Output: {out_dir}",
                f"Entries: {len(entries)}",
                f"Files written: {written_files}",
                f"Directories created: {written_dirs}",
                f"Encrypted files processed: {encrypted_files}",
                f"Data processed: {processed_bytes:,} bytes",
                f"Elapsed: {elapsed_seconds:.1f} seconds",
                f"Average throughput: {bytes_per_second / (1024 * 1024):.1f} MiB/s" if bytes_per_second else "Average throughput: -",
            ]
        ),
    }


def extract_pack_file(
    pack_path: str | Path,
    output_dir: str | Path,
    progress_cb: Any | None = None,
    cancel_cb: Any | None = None,
) -> dict[str, Any]:
    path = Path(pack_path)
    out_dir = Path(output_dir)
    entries = sorted(read_pack_entries(path), key=lambda entry: entry.file_offset)
    result = _extract_pack_entries(path, out_dir, entries, progress_cb=progress_cb, cancel_cb=cancel_cb)
    setup = record_advanced_content_setup_success(out_dir, path, out_dir, result)
    if setup.get("recorded"):
        result["advancedContentSetup"] = setup
    return result


def extract_selected_pack_entries(
    pack_path: str | Path,
    output_dir: str | Path,
    selection_text: str,
    progress_cb: Any | None = None,
    cancel_cb: Any | None = None,
) -> dict[str, Any]:
    path = Path(pack_path)
    out_dir = Path(output_dir)
    selectors = parse_pack_selection_lines(selection_text)
    if not selectors:
        raise ValueError("No package selection paths provided")
    entries = read_pack_entries(path)
    matched = filter_pack_entries(entries, selectors)
    if not matched:
        raise FileNotFoundError("No package entries matched the provided selection")
    result = _extract_pack_entries(path, out_dir, sorted(matched, key=lambda entry: entry.file_offset), progress_cb=progress_cb, cancel_cb=cancel_cb)
    result["selector_count"] = len(selectors)
    result["matched_count"] = len(matched)
    result["selectors"] = selectors
    result["summary"] = "\n".join(
        [
            f"Selected package entries extracted: {path.name}",
            f"Output: {out_dir}",
            f"Selectors: {len(selectors)}",
            f"Matched entries: {len(matched)}",
            f"Files written: {result.get('file_count', 0)}",
            f"Directories created: {result.get('directory_count', 0)}",
            f"Encrypted files processed: {result.get('encrypted_count', 0)}",
        ]
    )
    return result


def require_official_preview_preparation_complete(preparation: dict[str, Any]) -> None:
    if preparation.get("ready"):
        return
    failed = [row for row in preparation.get("failed", []) or [] if isinstance(row, dict)]
    details = "; ".join(
        f"{row.get('carId') or 'unknown car'}: {row.get('message') or 'unknown error'}"
        for row in failed[:8]
    )
    raise RuntimeError(
        "Official Preview Resources could not be completed"
        + (f": {details}" if details else ". No car previews were prepared.")
    )


def extract_official_car_resources(
    root: str | Path,
    progress_cb: Any | None = None,
    cancel_cb: Any | None = None,
) -> dict[str, Any]:
    game_root = Path(root).expanduser()
    if not game_root.exists() or not game_root.is_dir():
        raise FileNotFoundError(f"AC EVO root not found: {game_root}")
    pack_path = package_state.content_package_source_path(game_root, get_package_state_manifest_root())
    if pack_path is None:
        raise FileNotFoundError(
            "No valid AC EVO content package was found. Restore content.kspkg or keep one valid renamed content package in the AC EVO root."
        )
    preview_root = get_official_preview_assets_root(create=True)
    raw_preview_root = get_official_preview_raw_workspace_root(create=False)
    reuse = official_preview_pipeline.reusable_preview_assets(
        preview_root,
        pack_path,
        exporter_version=preview3d.GLB_EXPORTER_VERSION,
    )
    if reuse.get("reusable"):
        car_ids = [str(row) for row in reuse.get("carIds", []) or []]
        if progress_cb:
            progress_cb(
                {
                    "phase": "prepare_preview_assets",
                    "current": len(car_ids),
                    "total": len(car_ids),
                    "percent": 100.0,
                    "carId": car_ids[-1] if car_ids else "",
                    "previewStage": "done",
                    "previewMessage": "Existing preview assets are current",
                    "etaSeconds": 0,
                    "bytesProcessed": 0,
                    "totalBytes": 0,
                    "bytesPerSecond": 0,
                }
            )
        status = official_car_resources.inspect(
            preview_root,
            pack_path=pack_path,
            raw_preview_assets_root=raw_preview_root,
        )
        manifest = reuse.get("manifest") if isinstance(reuse.get("manifest"), dict) else {}
        preparation = manifest.get("derivedPreparation") if isinstance(manifest.get("derivedPreparation"), dict) else {}
        return {
            "ready": True,
            "reused": True,
            "reuseReason": reuse.get("reason") or "",
            "file": str(pack_path),
            "output_dir": str(official_car_resources.raw_root(raw_preview_root)),
            "entry_count": int(manifest.get("entryCount") or 0),
            "file_count": 0,
            "directory_count": 0,
            "encrypted_count": 0,
            "matched_count": int(manifest.get("entryCount") or 0),
            "rawCarCount": len(car_ids),
            "rawCarIds": car_ids[:500],
            "derivedPreparation": preparation,
            "previewAssetsRoot": str(preview_root),
            "rawRoot": str(official_car_resources.raw_root(raw_preview_root)),
            "rawContentCarsRoot": str(official_car_resources.raw_content_cars_root(raw_preview_root)),
            "derivedRoot": str(official_car_resources.derived_root(preview_root)),
            "manifestPath": str(official_car_resources.manifest_path(preview_root)),
            "manifest": manifest,
            "status": status,
            "summary": "\n".join(
                [
                    "Official preview assets are already current",
                    f"Source: {pack_path}",
                    f"Prepared cars reused: {len(car_ids)}",
                    "Package extraction and preview generation were skipped.",
                ]
            ),
        }
    entries = read_pack_entries(pack_path)
    matched = official_car_resources.filter_entries(entries)
    if not matched:
        raise FileNotFoundError("No official car resource entries matched content/cars actors, materials, meshes or texture descriptors")
    raw_root = official_car_resources.raw_root(raw_preview_root)
    path_preflight = official_preview_storage.inspect_extraction_paths(
        raw_root,
        matched,
        portable_root=get_portable_storage_root(),
    )
    if not path_preflight.get("ready"):
        runtime_long_paths = official_preview_storage.probe_runtime_long_path_support(
            raw_preview_root,
            required_path_length=int(path_preflight.get("longestPathLength") or 0),
        )
        path_preflight = official_preview_storage.apply_runtime_long_path_status(
            path_preflight,
            runtime_long_paths,
        )
    official_preview_storage.require_safe_extraction_paths(path_preflight)
    legacy_raw_cleanup = _cleanup_official_raw_cache(preview_root)
    stale_raw_cleanup = _cleanup_official_raw_cache(raw_preview_root)
    raw_root.mkdir(parents=True, exist_ok=True)
    official_car_resources.derived_root(preview_root, create=True)
    result = _extract_pack_entries(
        pack_path,
        raw_root,
        sorted(matched, key=lambda entry: entry.file_offset),
        progress_cb=progress_cb,
        cancel_cb=cancel_cb,
    )
    manifest = official_car_resources.write_manifest(
        preview_root,
        pack_path=pack_path,
        raw_preview_assets_root=raw_preview_root,
        matched_entries=matched,
        extraction_result=result,
    )
    manifest.update(official_preview_pipeline.package_signature(pack_path))
    manifest["previewPipelineVersion"] = official_preview_pipeline.PIPELINE_VERSION
    raw_car_ids = official_car_resources.list_raw_car_ids(raw_preview_root)
    preparation = _prepare_all_official_preview_assets(
        preview_root,
        raw_preview_root,
        raw_car_ids,
        progress_cb=progress_cb,
        cancel_cb=cancel_cb,
    )
    manifest["derivedPreparation"] = {
        key: value
        for key, value in preparation.items()
        if key != "results"
    }
    raw_cleanup: dict[str, Any] = {"removed": False, "path": str(raw_root), "reason": "preview asset preparation failed"}
    if preparation.get("ready"):
        raw_cleanup = _cleanup_official_raw_cache(raw_preview_root)
    manifest["rawCleanup"] = raw_cleanup
    manifest["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    _write_official_resources_manifest(preview_root, manifest)
    status = official_car_resources.inspect(
        preview_root,
        pack_path=pack_path,
        raw_preview_assets_root=raw_preview_root,
    )
    result.update(
        {
            "ready": bool(preparation.get("ready")),
            "matched_count": len(matched),
            "rawCarCount": len(raw_car_ids),
            "rawCarIds": raw_car_ids[:500],
            "derivedPreparation": preparation,
            "rawCleanup": raw_cleanup,
            "legacyRawCleanup": legacy_raw_cleanup,
            "staleRawCleanup": stale_raw_cleanup,
            "pathPreflight": path_preflight,
            "previewAssetsRoot": str(preview_root),
            "rawRoot": str(official_car_resources.raw_root(raw_preview_root)),
            "rawContentCarsRoot": str(official_car_resources.raw_content_cars_root(raw_preview_root)),
            "derivedRoot": str(official_car_resources.derived_root(preview_root)),
            "manifestPath": str(official_car_resources.manifest_path(preview_root)),
            "manifest": manifest,
            "status": status,
        }
    )
    result["summary"] = "\n".join(
        [
            "Official car resources extracted",
            f"Source: {pack_path}",
            f"Raw cache: {official_car_resources.raw_content_cars_root(raw_preview_root)}",
            f"Matched entries: {len(matched)}",
            f"Files written: {result.get('file_count', 0)}",
            f"Encrypted files processed: {result.get('encrypted_count', 0)}",
            f"Preview assets prepared: {preparation.get('preparedCount', 0)}/{preparation.get('carCount', 0)}",
            f"Raw cache cleanup: {'removed' if raw_cleanup.get('removed') else raw_cleanup.get('reason') or 'kept'}",
        ]
    )
    require_official_preview_preparation_complete(preparation)
    return result


def _find_official_raw_ao_texture(raw_car_root: Path) -> Path | None:
    if not raw_car_root.exists() or not raw_car_root.is_dir():
        return None
    candidates = [
        path
        for path in raw_car_root.rglob("*.texture")
        if path.is_file() and path.stem.lower() == "ext_skin_ao"
    ]
    if not candidates:
        return None

    def sort_key(path: Path) -> tuple[int, int, str]:
        rel_parts = [part.lower() for part in path.relative_to(raw_car_root).parts]
        in_textures_dir = 0 if "textures" in rel_parts[:-1] else 1
        has_mips = 0 if path.with_suffix(".texturemips").exists() else 1
        return (has_mips, in_textures_dir, norm_slashes(str(path)).lower())

    return sorted(candidates, key=sort_key)[0]


def _find_official_raw_thumbnail_texture(raw_car_root: Path) -> Path | None:
    folder = raw_car_root / "generated" / "thumbnails"
    if not folder.exists() or not folder.is_dir():
        return None
    candidates = [
        path
        for path in sorted(folder.glob("*.texture"), key=lambda row: row.name.lower())
        if path.is_file() and path.with_suffix(".texturemips").exists()
    ]
    if not candidates:
        return None
    usable = [path for path in candidates if not generated_thumbnail_template_skip_reason(path)]
    return (usable or candidates)[0]


def _write_official_resources_manifest(preview_root: Path, manifest: dict[str, Any]) -> None:
    official_car_resources.manifest_path(preview_root).write_text(
        json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False) + "\n",
        encoding="utf-8",
    )


def _cleanup_official_raw_cache(raw_preview_assets_root: Path) -> dict[str, Any]:
    return official_preview_storage.cleanup_raw_cache(
        raw_preview_assets_root,
        raw_dirname=official_car_resources.RAW_DIRNAME,
    )


def _prepare_all_official_preview_assets(
    preview_root: Path,
    raw_preview_assets_root: Path,
    car_ids: list[str],
    *,
    progress_cb: Any | None = None,
    cancel_cb: Any | None = None,
) -> dict[str, Any]:
    clean_ids = [official_car_resources.safe_car_id(car_id, "") for car_id in car_ids]
    clean_ids = [car_id for car_id in clean_ids if car_id]
    results: list[dict[str, Any]] = []
    failures: list[dict[str, Any]] = []
    warnings: list[str] = []
    total = len(clean_ids)
    run_cache = official_preview_pipeline.PreviewRunCache()
    tracker = official_preview_pipeline.PreviewProgress(total, progress_cb)
    started_at = time.perf_counter()
    car_timings: list[dict[str, Any]] = []
    for index, car_id in enumerate(clean_ids, start=1):
        if cancel_cb and cancel_cb():
            raise PackageExtractionCancelled("Official preview asset preparation cancelled")
        tracker.emit(index, car_id, "start", message="Starting preview preparation")
        car_started_at = time.perf_counter()

        def car_progress(stage: str, current: int = 0, stage_total: int = 0, message: str = "") -> None:
            tracker.emit(
                index,
                car_id,
                stage,
                current=current,
                stage_total=stage_total,
                message=message,
            )

        try:
            prepared = prepare_official_preview_assets(
                car_id,
                force=True,
                raw_preview_assets_root=raw_preview_assets_root,
                run_cache=run_cache,
                asset_progress_cb=car_progress,
                cancel_cb=cancel_cb,
            )
            if prepared.get("ready") is not True:
                raise RuntimeError(
                    str(prepared.get("reason") or "Generated preview assets did not pass output validation")
                )
            results.append(prepared)
            warnings.extend(str(row) for row in prepared.get("warnings", []) or [])
        except PackageExtractionCancelled:
            raise
        except Exception as exc:
            failures.append({"carId": car_id, "message": str(exc)})
            tracker.emit(
                index,
                car_id,
                "done",
                current=1,
                stage_total=1,
                message=f"Preview preparation failed: {exc}",
            )
        else:
            tracker.emit(index, car_id, "done", current=1, stage_total=1, message="Preview assets ready")
        duration = max(0.0, time.perf_counter() - car_started_at)
        car_timings.append({"carId": car_id, "durationSeconds": round(duration, 2)})
    elapsed_seconds = max(0.0, time.perf_counter() - started_at)
    return {
        "ready": total > 0 and not failures and len(results) == total,
        "carCount": total,
        "preparedCount": len(results),
        "failedCount": len(failures),
        "preparedCarIds": [str(row.get("carId") or "") for row in results if str(row.get("carId") or "")],
        "failed": failures,
        "warnings": warnings,
        "results": results,
        "elapsedSeconds": round(elapsed_seconds, 2),
        "carTimings": car_timings,
        "stageTimings": tracker.timing_summary(),
        "cache": run_cache.summary(),
    }


def _official_variant_content_rel(root: Path, path: Path) -> str:
    rel = content_rel_from_disk(root, path)
    return norm_slashes(rel or str(path))


def _official_variant_path_label(value: str) -> str:
    stem = Path(norm_slashes(str(value or "")).replace("\\", "/")).stem
    text = re.sub(r"^(preset_|mechanical_|visual_|mech_|vis_)+", "", stem, flags=re.I)
    text = re.sub(r"(_mechanical|_visual|_mech|_vis)$", "", text, flags=re.I)
    text = re.sub(r"[_-]+", " ", text).strip()
    return text.title() if text else stem or "Default"


def _official_variant_label(mechanical_info: dict[str, Any], visual_info: dict[str, Any], mechanical_rel: str, visual_rel: str) -> str:
    for key in ("short_name", "name"):
        value = str(mechanical_info.get(key) or "").strip()
        if value:
            return value
    for key in ("presentation", "name"):
        value = str(visual_info.get(key) or "").strip()
        if value:
            return value
    return _official_variant_path_label(mechanical_rel or visual_rel)


def _official_variant_id(*parts: str) -> str:
    source = "|".join(norm_slashes(str(part or "")).lower() for part in parts if str(part or "").strip())
    seed = re.sub(r"[^a-z0-9]+", "_", source.lower()).strip("_")[:54] or "default"
    digest = hashlib.sha1(source.encode("utf-8")).hexdigest()[:10] if source else "default"
    return f"{seed}_{digest}" if digest not in seed else seed


def _official_variant_compatiblepart_refs(
    root: Path,
    mechanical_rel: str,
    finalstate_rel: str,
    mechanical_info: dict[str, Any],
) -> list[str]:
    refs: list[str] = []
    for rel in (mechanical_rel, finalstate_rel):
        path = resolve_content_path(root, rel)
        if not path.exists():
            continue
        try:
            refs.extend(_proto_content_refs(path.read_bytes(), (".compatiblepart",)))
        except Exception:
            continue
    refs.extend(str(row) for row in mechanical_info.get("aftermarket_parts", []) or [])
    return _official_existing_compatiblepart_refs(root, refs, include_aftermarket=False)


def _official_existing_compatiblepart_refs(root: Path, refs: Iterable[Any], *, include_aftermarket: bool = False) -> list[str]:
    clean: list[str] = []
    for raw in refs:
        ref = normalize_game_rel_path(str(raw or ""))
        if not ref:
            continue
        ref_lc = ref.lower()
        if not ref_lc.endswith(".compatiblepart"):
            continue
        if not include_aftermarket and "\\presets\\aftermarket\\" in ref_lc:
            continue
        if not _carpart_ref_from_compatiblepart(root, ref):
            continue
        clean.append(ref)
    return variant_parts.unique_compatible_part_refs(clean)


def _official_variant_context_values(
    mechanical_info: dict[str, Any],
    visual_info: dict[str, Any],
    mechanical_rel: str,
    visual_rel: str,
    finalstate_rel: str,
) -> list[str]:
    values = [
        mechanical_rel,
        visual_rel,
        finalstate_rel,
    ]
    for info in (mechanical_info, visual_info):
        if not isinstance(info, dict):
            continue
        for key in (
            "name",
            "short_name",
            "presentation",
            "visual_preset",
            "cardesign",
            "rim_design",
        ):
            value = str(info.get(key) or "").strip()
            if value:
                values.append(value)
    return values


def _official_contextual_compatiblepart_refs(root: Path, refs: Iterable[Any], context_values: Iterable[Any]) -> list[str]:
    filtered: list[str] = []
    for ref in refs:
        compatible_ref = normalize_game_rel_path(str(ref or ""))
        if not compatible_ref:
            continue
        carpart_ref = _carpart_ref_from_compatiblepart(root, compatible_ref)
        if official_variant_parts.compatible_part_allowed_for_context(compatible_ref, carpart_ref, context_values):
            filtered.append(compatible_ref)
    return variant_parts.unique_compatible_part_refs(filtered)


def _official_actor_static_compatiblepart_refs(root: Path, raw_car_root: Path) -> list[str]:
    refs: list[str] = []
    for actor_path in sorted(raw_car_root.glob("*.actor"), key=lambda item: item.name.lower()):
        try:
            actor_data = actor_path.read_bytes()
        except Exception:
            continue
        try:
            refs.extend(actor_list_compatibleparts(actor_data))
        except Exception:
            pass
        try:
            refs.extend(_proto_content_refs(actor_data, (".compatiblepart",)))
        except Exception:
            pass
    filtered = [
        ref
        for ref in refs
        if "\\presets\\parts\\" in normalize_game_rel_path(str(ref or "")).lower()
    ]
    return _official_existing_compatiblepart_refs(root, filtered, include_aftermarket=False)


def _official_cached_mesh_uv(
    run_cache: official_preview_pipeline.PreviewRunCache | None,
    mesh_path: Path,
    root: Path,
) -> dict[str, Any]:
    if run_cache is None:
        return inspect_mesh_uv_view(mesh_path, root)
    key = official_preview_pipeline.file_cache_key(mesh_path)
    cached = run_cache.mesh_uv.get(key)
    if cached is not None:
        run_cache.hit("meshUvHits")
        return dict(cached)
    run_cache.hit("meshUvMisses")
    inspected = inspect_mesh_uv_view(mesh_path, root)
    run_cache.mesh_uv[key] = inspected
    return dict(inspected)


def _official_cached_mesh_info(
    run_cache: official_preview_pipeline.PreviewRunCache | None,
    mesh_path: Path,
    root: Path,
) -> dict[str, Any]:
    if run_cache is None:
        return inspect_mesh_readable(mesh_path, root=root)
    key = official_preview_pipeline.file_cache_key(mesh_path)
    cached = run_cache.mesh_info.get(key)
    if cached is not None and cached.get("positions") and cached.get("indices"):
        run_cache.hit("meshInfoHits")
        return dict(cached)
    if cached is not None:
        run_cache.mesh_info.pop(key, None)
    run_cache.hit("meshInfoMisses")
    inspected = inspect_mesh_readable(mesh_path, root=root)
    run_cache.mesh_info[key] = inspected
    return dict(inspected)


def _official_cached_mesh_materials(
    run_cache: official_preview_pipeline.PreviewRunCache | None,
    root: Path,
    mesh_path: Path,
) -> list[dict[str, Any]]:
    if run_cache is None:
        return _variant_analysis_mesh_materials(root, mesh_path)
    key = official_preview_pipeline.file_cache_key(mesh_path)
    cached = run_cache.mesh_materials.get(key)
    if cached is not None:
        run_cache.hit("meshMaterialHits")
        return [dict(row) for row in cached]
    run_cache.hit("meshMaterialMisses")
    inspected = _variant_analysis_mesh_materials(
        root,
        mesh_path,
        mesh_uv_loader=lambda path, game_root: _official_cached_mesh_uv(run_cache, path, game_root),
    )
    run_cache.mesh_materials[key] = inspected
    return [dict(row) for row in inspected]


def _official_cached_variant_preview(
    run_cache: official_preview_pipeline.PreviewRunCache | None,
    entry: dict[str, Any],
    root: Path,
    compatible_refs: list[str],
) -> dict[str, Any]:
    if run_cache is None:
        return _resolve_livery_variant_part_meshes(entry, root, compatible_refs=compatible_refs)
    cache_key = (
        str(entry.get("car_model") or entry.get("car_content_path") or "").lower(),
        tuple(sorted(norm_slashes(str(row or "")).lower() for row in compatible_refs)),
    )
    cached = run_cache.variant_previews.get(cache_key)
    if cached is not None:
        run_cache.hit("variantPreviewHits")
        return copy.deepcopy(cached)
    run_cache.hit("variantPreviewMisses")
    preview = _resolve_livery_variant_part_meshes(
        entry,
        root,
        compatible_refs=compatible_refs,
        mesh_uv_loader=lambda path, game_root: _official_cached_mesh_uv(run_cache, path, game_root),
    )
    run_cache.variant_previews[cache_key] = copy.deepcopy(preview)
    return preview


def _official_preview_glb_mesh_info(
    mesh_path: Path,
    root: Path,
    run_cache: official_preview_pipeline.PreviewRunCache | None = None,
) -> dict[str, Any]:
    mesh_info = _official_cached_mesh_info(run_cache, mesh_path, root)
    mesh_info["component_split_materials"] = [
        "EXT_SKIN",
        "EXT_DETAILS_CHROME",
        "EXT_DETAILS_PLASTIC",
        "EXT_DETAILS_PLASTIC_GLOSS",
        "EXT_DETAILS_RUBBER",
    ]
    mesh_info["minimum_component_triangles"] = 1
    return mesh_info


def _official_write_cached_aux_model(
    run_cache: official_preview_pipeline.PreviewRunCache | None,
    mesh_path: Path,
    glb_path: Path,
    root: Path,
    *,
    force: bool,
) -> dict[str, Any]:
    if run_cache is None:
        return _write_preview_aux_model(mesh_path, glb_path, root, force=force)
    source_key = (*official_preview_pipeline.file_cache_key(mesh_path), int(preview3d.GLB_EXPORTER_VERSION))
    cached = run_cache.auxiliary_models.get(source_key)
    if cached is None:
        run_cache.hit("auxModelMisses")
        mesh_info = _official_cached_mesh_info(run_cache, mesh_path, root)
        result = _write_preview_aux_model(mesh_path, glb_path, root, force=force, mesh_info=mesh_info)
        run_cache.auxiliary_models[source_key] = {
            "glbPath": str(glb_path),
            "metaPath": str(glb_path.with_suffix(".meta.json")),
            "result": dict(result),
        }
        return result
    run_cache.hit("auxModelHits")
    source_glb = Path(str(cached.get("glbPath") or ""))
    source_meta = Path(str(cached.get("metaPath") or ""))
    glb_path.parent.mkdir(parents=True, exist_ok=True)
    if source_glb.resolve() != glb_path.resolve():
        shutil.copy2(source_glb, glb_path)
        target_meta = glb_path.with_suffix(".meta.json")
        if source_meta.exists():
            try:
                meta = json.loads(source_meta.read_text(encoding="utf-8"))
            except Exception:
                meta = {}
            meta["glbPath"] = str(glb_path)
            meta["reusedFrom"] = str(source_glb)
            target_meta.write_text(json.dumps(meta, indent=2, ensure_ascii=False), encoding="utf-8")
    result = dict(cached.get("result") or {})
    result.update({"path": str(glb_path), "cached": True, "reused": True, "reusedFrom": str(source_glb)})
    return result


def _official_parts_uv_cache_key(meshes: list[dict[str, Any]], uv_size: int) -> tuple[Any, ...]:
    keys: list[tuple[str, int, int]] = []
    for mesh in meshes:
        source = Path(str(mesh.get("file") or ""))
        if source.exists() and source.is_file():
            keys.append(official_preview_pipeline.file_cache_key(source))
    return int(uv_size), *sorted(keys)


def _official_write_cached_parts_uv(
    run_cache: official_preview_pipeline.PreviewRunCache | None,
    meshes: list[dict[str, Any]],
    target: Path,
    *,
    uv_size: int,
) -> dict[str, Any]:
    if run_cache is None:
        return uv_templates.write_ext_skin_parts_uv_template(meshes, target, size=int(uv_size), overwrite=True)
    cache_key = _official_parts_uv_cache_key(meshes, uv_size)
    cached = run_cache.parts_uv.get(cache_key)
    if cached is None:
        run_cache.hit("partsUvMisses")
        result = uv_templates.write_ext_skin_parts_uv_template(meshes, target, size=int(uv_size), overwrite=True)
        if target.exists():
            run_cache.parts_uv[cache_key] = {"path": str(target)}
        return result
    run_cache.hit("partsUvHits")
    source = Path(str(cached.get("path") or ""))
    target.parent.mkdir(parents=True, exist_ok=True)
    if source.exists() and source.resolve() != target.resolve():
        shutil.copy2(source, target)
    result = uv_templates.write_ext_skin_parts_uv_template(meshes, target, size=int(uv_size), overwrite=False)
    result.update({"cached": True, "reused": True, "reusedFrom": str(source)})
    return result


def _official_variant_policy_from_preview(preview: dict[str, Any], compatible_source: str = "") -> dict[str, Any]:
    mesh_refs = preview.get("meshRefs") if isinstance(preview.get("meshRefs"), list) else []
    skin_refs = preview.get("skinMeshRefs") if isinstance(preview.get("skinMeshRefs"), list) else []
    relevant_refs = [
        row for row in mesh_refs
        if isinstance(row, dict) and (row.get("previewRelevant") or row.get("skinRelevant"))
    ]
    mesh_text = " ".join(str(row.get("relPath") or "") for row in mesh_refs if isinstance(row, dict)).lower()
    replacing = any(isinstance(row, dict) and row.get("replacesExterior") for row in mesh_refs) or "drift" in mesh_text
    source = str(compatible_source or "").strip().lower()
    if not mesh_refs:
        return variant_parts.build_policy(variant_parts.CLASS_NONE, "high")
    if replacing and source == "actor-static":
        return variant_parts.build_policy(variant_parts.CLASS_NONE, "medium")
    if replacing:
        return variant_parts.build_policy(variant_parts.CLASS_HYBRID_REPLACEMENT, "medium")
    if not relevant_refs:
        return variant_parts.build_policy(variant_parts.CLASS_NON_LIVERY, "medium")
    if source == "actor-static":
        safe_actor_slots = {"hardtop", "splitter", "spoiler", "wing"}
        slots = {
            variant_parts.part_slot(
                str(row.get("compatiblePartRel") or ""),
                str(row.get("carPartRel") or ""),
            )
            for row in relevant_refs
            if isinstance(row, dict)
        }
        if any(slot and slot not in safe_actor_slots for slot in slots):
            return variant_parts.build_policy(variant_parts.CLASS_NEEDS_REVIEW, "low")
    if skin_refs:
        return variant_parts.build_policy(variant_parts.CLASS_SHARED_ADDITIVE, "medium")
    return variant_parts.build_policy(variant_parts.CLASS_NON_LIVERY, "medium")


def _official_variant_policy_allows_assets(policy: dict[str, Any]) -> bool:
    classification = str(policy.get("classification") or "").strip()
    return classification in {
        variant_parts.CLASS_SHARED_ADDITIVE,
        variant_parts.CLASS_SHARED_ADDITIVE_VARIANT_AO,
        variant_parts.CLASS_FULL_VARIANT,
        variant_parts.CLASS_HYBRID_REPLACEMENT,
    }


def _official_variant_ao_assets(
    root: Path,
    derived_root: Path,
    entry: dict[str, Any],
    variant_id: str,
    preview: dict[str, Any],
    run_cache: official_preview_pipeline.PreviewRunCache | None = None,
) -> dict[str, Any]:
    base_material = _variant_analysis_base_skin_material(root, entry)
    base_ao_refs = _variant_analysis_texture_rel_set([base_material], "aoTextureRefs")
    material_summaries: list[dict[str, Any]] = []
    for mesh_ref in preview.get("meshRefs", []) or []:
        if not isinstance(mesh_ref, dict) or not (mesh_ref.get("previewRelevant") or mesh_ref.get("skinRelevant")):
            continue
        mesh_path = Path(str(mesh_ref.get("path") or ""))
        if not mesh_path.exists():
            continue
        material_summaries.extend(_official_cached_mesh_materials(run_cache, root, mesh_path))
    selected = official_variant_parts.select_variant_ao_source(base_ao_refs, material_summaries)
    if not selected:
        return {}
    if not str(selected.get("sourceTexturePath") or "").strip():
        source_rel = str(selected.get("sourceRel") or selected.get("sourceValue") or "").strip()
        if source_rel:
            selected["sourceTexturePath"] = str(resolve_content_path(root, source_rel))
    target = derived_root / "variant_parts" / preview3d.safe_segment(variant_id, "variant") / preview3d.LIVE_AO_TEXTURE_NAME
    return official_variant_parts.variant_ao_metadata(selected, target)


def _prepare_official_variant_ao_textures(vehicle_variant_data: dict[str, Any], *, force: bool = False) -> list[str]:
    warnings: list[str] = []
    variants = vehicle_variant_data.get("vehicleVariants") if isinstance(vehicle_variant_data.get("vehicleVariants"), list) else []
    for row in variants:
        if not isinstance(row, dict):
            continue
        variant_ao = row.get("variantAoTexture") if isinstance(row.get("variantAoTexture"), dict) else {}
        if not variant_ao:
            continue
        source = Path(str(variant_ao.get("sourceTexturePath") or ""))
        target = Path(str(variant_ao.get("path") or row.get("aoTexturePath") or ""))
        if not source.exists() or not source.is_file():
            warnings.append(f"Official variant AO source not found for {row.get('label') or row.get('id')}: {source}")
            continue
        mips = Path(str(variant_ao.get("sourceTexturemipsPath") or source.with_suffix(".texturemips")))
        if not mips.exists() or not mips.is_file():
            warnings.append(f"Official variant AO texturemips not found for {row.get('label') or row.get('id')}: {mips}")
            continue
        overwrite = bool(force or not target.exists() or int(mips.stat().st_mtime * 1000) > int(target.stat().st_mtime * 1000))
        try:
            converted = convert_texture_to_png(source, target, overwrite=overwrite, create_dds=False)
        except Exception as exc:
            variant_ao.update({"status": "conversion_failed", "reason": str(exc), "exists": target.exists() and target.is_file()})
            row["variantAoTexture"] = _json_ready(variant_ao)
            warnings.append(f"Official variant AO conversion failed for {row.get('label') or row.get('id')}: {exc}")
            continue
        variant_ao.update(
            {
                "exists": target.exists() and target.is_file(),
                "path": str(target),
                "name": target.name,
                "status": "generated" if not converted.get("skipped") else "current",
                "reason": converted.get("reason") or "",
            }
        )
        if target.exists() and target.is_file():
            row["aoPolicy"] = "variant"
            row["aoTexturePath"] = str(target)
            row["aoSourceTexturePath"] = str(source)
            row["aoSourceTexturemipsPath"] = str(mips)
            row["variantAoTexture"] = _json_ready(variant_ao)
    return warnings


def _official_variant_part_assets(
    root: Path,
    derived_root: Path,
    car_id: str,
    variant_id: str,
    compatible_refs: list[str],
    *,
    compatible_source: str = "official_variant",
    force: bool = False,
    uv_size: int = 4096,
    run_cache: official_preview_pipeline.PreviewRunCache | None = None,
    asset_progress_cb: Any | None = None,
    variant_index: int = 0,
    variant_total: int = 0,
) -> dict[str, Any]:
    if not compatible_refs:
        return {"compatiblePartSource": "none", "compatibleParts": [], "variantPartPreview": {}, "partsUvTemplate": {}, "warnings": []}
    entry = {
        "car_model": car_id,
        "car_content_path": f"content\\cars\\{car_id}",
    }
    warnings: list[str] = []
    preview = _official_cached_variant_preview(run_cache, entry, root, compatible_refs)
    warnings.extend(str(row) for row in preview.get("warnings", []) or [])
    policy = _official_variant_policy_from_preview(preview, compatible_source)
    classification = str(policy.get("classification") or "").strip()
    if classification != variant_parts.CLASS_FULL_VARIANT:
        preview["hideExteriorModel"] = False
    variant_ao = _official_variant_ao_assets(root, derived_root, entry, variant_id, preview, run_cache=run_cache)
    variant_ao_ready = bool(variant_ao.get("ready"))
    policy = official_variant_parts.policy_with_variant_ao(policy, variant_ao_ready)
    surface_policies = official_variant_parts.surface_policies_with_variant_ao(policy.get("surfaces") or [], variant_ao_ready)
    if not _official_variant_policy_allows_assets(policy):
        return {
            "compatiblePartSource": compatible_source,
            "compatibleParts": compatible_refs,
            "variantPartPreview": {},
            "variantPolicy": _json_ready(policy),
            "surfacePolicies": _json_ready(surface_policies),
            "partsUvTemplate": {},
            "partsUvTemplatePath": "",
            "aoPolicy": "variant" if variant_ao_ready else "base",
            "variantAoTexture": _json_ready(variant_ao) if variant_ao else {},
            "aoTexturePath": "",
            "aoSourceTexturePath": str(variant_ao.get("sourceTexturePath") or "") if variant_ao else "",
            "aoSourceTexturemipsPath": str(variant_ao.get("sourceTexturemipsPath") or "") if variant_ao else "",
            "warnings": warnings,
        }
    variant_dir = derived_root / "variant_parts" / preview3d.safe_segment(variant_id, "variant")
    auxiliary_models: list[dict[str, Any]] = []
    if preview.get("meshRefs"):
        variant_dir.mkdir(parents=True, exist_ok=True)
    for mesh_ref in preview.get("meshRefs", []) or []:
        if not isinstance(mesh_ref, dict) or not mesh_ref.get("previewRelevant"):
            continue
        mesh_path = Path(str(mesh_ref.get("path") or ""))
        if not mesh_path.exists():
            continue
        label = f"part_{preview3d.safe_segment(mesh_ref.get('label'), 'variant')}"
        aux_glb_path = _preview_variant_part_model_cache_path(variant_dir, label, mesh_path)
        try:
            if asset_progress_cb:
                asset_progress_cb("variants", variant_index, variant_total, f"Preparing variant mesh {mesh_path.name}")
            aux = _official_write_cached_aux_model(run_cache, mesh_path, aux_glb_path, root, force=force)
            aux["label"] = label
            aux["kind"] = "variantPart"
            aux["sourceRelPath"] = mesh_ref.get("relPath")
            aux["compatiblePartRel"] = mesh_ref.get("compatiblePartRel")
            aux["carPartRel"] = mesh_ref.get("carPartRel")
            aux["skinRelevant"] = bool(mesh_ref.get("skinRelevant"))
            aux["skinTriangleCount"] = int(mesh_ref.get("skinTriangleCount") or 0)
            aux["transform"] = mesh_ref.get("transform") if isinstance(mesh_ref.get("transform"), dict) else _variant_part_preview_transform(entry, str(mesh_ref.get("relPath") or ""))
            auxiliary_models.append(aux)
        except Exception as exc:
            warnings.append(f"Official variant part GLB skipped for {mesh_ref.get('relPath') or mesh_path}: {exc}")
    if auxiliary_models:
        preview["auxiliaryModels"] = auxiliary_models
        preview["auxiliaryModelPaths"] = [str(row.get("path")) for row in auxiliary_models if row.get("path")]
    parts_uv_result: dict[str, Any] = {}
    variant_skin_mesh_uvs: list[dict[str, Any]] = []
    for mesh_ref in preview.get("skinMeshRefs", []) or []:
        if not isinstance(mesh_ref, dict):
            continue
        mesh_path = Path(str(mesh_ref.get("path") or ""))
        if not mesh_path.exists():
            continue
        try:
            mesh_uv = _official_cached_mesh_uv(run_cache, mesh_path, root)
            mesh_uv["variantPartSlot"] = variant_parts.part_slot(
                str(mesh_ref.get("compatiblePartRel") or ""),
                str(mesh_ref.get("carPartRel") or ""),
            )
            mesh_uv["variantPartRelPath"] = str(mesh_ref.get("relPath") or "")
            variant_skin_mesh_uvs.append(mesh_uv)
        except Exception as exc:
            warnings.append(f"Official variant part UV skipped for {mesh_ref.get('relPath') or mesh_path}: {exc}")
    if variant_skin_mesh_uvs:
        parts_uv_path = variant_dir / uv_templates.PARTS_UV_TEMPLATE_NAME
        try:
            if asset_progress_cb:
                asset_progress_cb("variants", variant_index, variant_total, "Preparing variant UV template")
            parts_uv_result = _official_write_cached_parts_uv(
                run_cache,
                variant_skin_mesh_uvs,
                parts_uv_path,
                uv_size=int(uv_size),
            )
            parts_uv_result["variantPartMeshCount"] = len(variant_skin_mesh_uvs)
        except Exception as exc:
            warnings.append(f"Official variant parts UV template skipped for {variant_id}: {exc}")
            parts_uv_result = {}
    return {
        "compatiblePartSource": compatible_source,
        "compatibleParts": compatible_refs,
        "variantPartPreview": _json_ready(preview) if preview.get("meshRefs") else {},
        "variantPolicy": _json_ready(policy),
        "surfacePolicies": _json_ready(surface_policies),
        "partsUvTemplate": _json_ready(parts_uv_result) if parts_uv_result else {},
        "partsUvTemplatePath": str(parts_uv_result.get("path") or "") if parts_uv_result else "",
        "aoPolicy": "variant" if variant_ao_ready else "base",
        "variantAoTexture": _json_ready(variant_ao) if variant_ao else {},
        "aoTexturePath": "",
        "aoSourceTexturePath": str(variant_ao.get("sourceTexturePath") or "") if variant_ao else "",
        "aoSourceTexturemipsPath": str(variant_ao.get("sourceTexturemipsPath") or "") if variant_ao else "",
        "warnings": warnings,
    }


def _official_variant_parts_uv_signature(row: dict[str, Any]) -> tuple[str, ...]:
    template = row.get("partsUvTemplate") if isinstance(row.get("partsUvTemplate"), dict) else {}
    meshes = template.get("meshes") if isinstance(template.get("meshes"), list) else []
    refs = [
        norm_slashes(str(mesh.get("variantPartRelPath") or mesh.get("file") or "")).lower()
        for mesh in meshes
        if isinstance(mesh, dict) and str(mesh.get("variantPartRelPath") or mesh.get("file") or "").strip()
    ]
    if refs:
        return tuple(sorted(refs))
    return tuple(sorted(norm_slashes(str(ref or "")).lower() for ref in row.get("compatibleParts", []) or [] if str(ref or "").strip()))


def _promote_official_root_parts_uv_template(rows: list[dict[str, Any]], derived_root: Path) -> list[str]:
    candidates: list[tuple[dict[str, Any], Path, tuple[str, ...]]] = []
    for row in rows:
        if not isinstance(row, dict):
            continue
        template = row.get("partsUvTemplate") if isinstance(row.get("partsUvTemplate"), dict) else {}
        path_text = str(row.get("partsUvTemplatePath") or template.get("path") or "").strip()
        if not path_text:
            continue
        source = Path(path_text)
        signature = _official_variant_parts_uv_signature(row)
        if source.exists() and source.is_file() and signature:
            candidates.append((row, source, signature))
    if not candidates:
        return []
    signatures = {signature for _row, _source, signature in candidates}
    if len(signatures) != 1:
        return ["Official variant parts UV kept per-variant because multiple distinct part UV sets were found."]
    target = derived_root / uv_templates.PARTS_UV_TEMPLATE_NAME
    source = candidates[0][1]
    warnings: list[str] = []
    try:
        target.parent.mkdir(parents=True, exist_ok=True)
        if source.resolve() != target.resolve():
            shutil.copy2(source, target)
    except Exception as exc:
        return [f"Official root parts UV template skipped: {exc}"]
    for row, _source, _signature in candidates:
        template = row.get("partsUvTemplate") if isinstance(row.get("partsUvTemplate"), dict) else {}
        updated = {
            **template,
            "path": str(target),
            "name": target.name,
            "exists": target.exists(),
            "source": "official_vehicle_variant_root",
        }
        row["partsUvTemplate"] = _json_ready(updated)
        row["partsUvTemplatePath"] = str(target)
        preview = row.get("variantPartPreview") if isinstance(row.get("variantPartPreview"), dict) else {}
        if preview:
            preview["partsUvTemplatePath"] = str(target)
            row["variantPartPreview"] = preview
    return warnings


def _apply_official_hidden_component_materials(vehicle_variant_data: dict[str, Any], glb: dict[str, Any]) -> None:
    if not isinstance(vehicle_variant_data, dict) or not isinstance(glb, dict):
        return
    variants = vehicle_variant_data.get("vehicleVariants") if isinstance(vehicle_variant_data.get("vehicleVariants"), list) else []
    for row in variants:
        if not isinstance(row, dict):
            continue
        policy = row.get("variantPolicy") if isinstance(row.get("variantPolicy"), dict) else {}
        classification = str(policy.get("classification") or "")
        preview = row.get("variantPartPreview") if isinstance(row.get("variantPartPreview"), dict) else {}
        if not preview.get("meshRefs"):
            continue
        if classification == variant_parts.CLASS_HYBRID_REPLACEMENT:
            hidden_component_materials = _hybrid_hidden_exterior_component_materials(glb, preview)
        elif classification == variant_parts.CLASS_SHARED_ADDITIVE:
            hidden_component_materials = _shared_additive_hidden_exterior_component_materials(glb, preview)
        else:
            hidden_component_materials = []
        if hidden_component_materials:
            preview["hiddenExteriorComponentMaterials"] = hidden_component_materials
            row["hiddenExteriorComponentMaterials"] = hidden_component_materials
            row["hiddenExteriorMaterialNames"] = hidden_component_materials
            row["variantPartPreview"] = preview
        else:
            preview.pop("hiddenExteriorComponentMaterials", None)
            row.pop("hiddenExteriorComponentMaterials", None)
            row.pop("hiddenExteriorMaterialNames", None)


def _official_preview_vehicle_variants(
    raw_root: Path,
    raw_car_root: Path,
    derived_root: Path,
    car_id: str,
    *,
    force: bool = False,
    uv_size: int = 4096,
    run_cache: official_preview_pipeline.PreviewRunCache | None = None,
    asset_progress_cb: Any | None = None,
    cancel_cb: Any | None = None,
) -> dict[str, Any]:
    warnings: list[str] = []
    if asset_progress_cb:
        asset_progress_cb("scan", 0, 4, "Scanning actors and vehicle presets")
    actor_visuals: set[str] = set()
    actor_mechanicals: set[str] = set()
    actor_static_compatible_refs = _official_actor_static_compatiblepart_refs(raw_root, raw_car_root)
    for actor_path in sorted(raw_car_root.glob("*.actor"), key=lambda item: item.name.lower()):
        try:
            actor_data = actor_path.read_bytes()
            actor_visuals.update(norm_slashes(str(row)).lower() for row in actor_list_visualpresets(actor_data) if str(row or "").strip())
            actor_mechanicals.update(norm_slashes(str(row)).lower() for row in actor_list_mechanicalpresets(actor_data) if str(row or "").strip())
        except Exception as exc:
            warnings.append(f"Could not read official actor presets for {car_id}: {exc}")
    if asset_progress_cb:
        asset_progress_cb("scan", 1, 4, "Scanning visual presets")

    visual_infos: dict[str, dict[str, Any]] = {}
    for visual_path in sorted(raw_car_root.rglob("*.visualcarpreset"), key=lambda item: str(item).lower()):
        rel = _official_variant_content_rel(raw_root, visual_path)
        try:
            visual_infos[rel.lower()] = {"path": visual_path, "rel": rel, "info": extract_visualpreset_info(visual_path.read_bytes())}
        except Exception as exc:
            warnings.append(f"Could not read official visual preset {visual_path.name}: {exc}")
    if asset_progress_cb:
        asset_progress_cb("scan", 2, 4, "Scanning mechanical presets")

    mechanical_infos: dict[str, dict[str, Any]] = {}
    for mechanical_path in sorted(raw_car_root.rglob("*.mechanicalcarpreset"), key=lambda item: str(item).lower()):
        rel = _official_variant_content_rel(raw_root, mechanical_path)
        try:
            mechanical_infos[rel.lower()] = {"path": mechanical_path, "rel": rel, "info": extract_mechanicalpreset_info(mechanical_path.read_bytes())}
        except Exception as exc:
            warnings.append(f"Could not read official mechanical preset {mechanical_path.name}: {exc}")
    if asset_progress_cb:
        asset_progress_cb("scan", 3, 4, "Scanning car final states")

    finalstate_by_pair: dict[tuple[str, str], dict[str, Any]] = {}
    finalstate_by_visual: dict[str, dict[str, Any]] = {}
    for finalstate_path in sorted(raw_car_root.rglob("*.carfinalstate"), key=lambda item: str(item).lower()):
        rel = _official_variant_content_rel(raw_root, finalstate_path)
        try:
            info = extract_carfinalstate_info(finalstate_path.read_bytes())
        except Exception as exc:
            warnings.append(f"Could not read official car final state {finalstate_path.name}: {exc}")
            continue
        mechanical_key = norm_slashes(str(info.get("mechanical_preset") or "")).lower()
        visual_key = norm_slashes(str(info.get("visual_preset") or "")).lower()
        row = {"rel": rel, "info": info}
        if mechanical_key and visual_key:
            finalstate_by_pair[(mechanical_key, visual_key)] = row
        if visual_key:
            finalstate_by_visual.setdefault(visual_key, row)

    candidate_mechanicals = sorted(actor_mechanicals.intersection(mechanical_infos.keys()) or mechanical_infos.keys())
    estimated_variant_total = sum(
        max(1, len((mechanical_infos.get(key, {}).get("info") or {}).get("visual_presets", []) or []))
        for key in candidate_mechanicals
    )
    if not estimated_variant_total:
        estimated_variant_total = max(1, len(visual_infos))
    if asset_progress_cb:
        asset_progress_cb("scan", 4, 4, "Vehicle variants indexed")
    rows: list[dict[str, Any]] = []
    seen: set[tuple[str, str, str]] = set()

    for mechanical_key in candidate_mechanicals:
        mechanical_row = mechanical_infos.get(mechanical_key)
        if not mechanical_row:
            continue
        mechanical_rel = str(mechanical_row.get("rel") or "")
        mechanical_info = mechanical_row.get("info") if isinstance(mechanical_row.get("info"), dict) else {}
        visual_refs = [norm_slashes(str(row)) for row in mechanical_info.get("visual_presets", []) if str(row or "").strip()]
        if actor_visuals:
            filtered_refs = [row for row in visual_refs if row.lower() in actor_visuals]
            if filtered_refs:
                visual_refs = filtered_refs
        if not visual_refs and mechanical_info.get("visual_preset"):
            visual_refs = [norm_slashes(str(mechanical_info.get("visual_preset")))]
        if not visual_refs:
            visual_refs = [""]
        for visual_rel in visual_refs:
            if cancel_cb and cancel_cb():
                raise PackageExtractionCancelled("Official preview asset preparation cancelled")
            visual_key = visual_rel.lower()
            visual_row = visual_infos.get(visual_key, {})
            visual_info = visual_row.get("info") if isinstance(visual_row.get("info"), dict) else {}
            finalstate_row = finalstate_by_pair.get((mechanical_key, visual_key)) or finalstate_by_visual.get(visual_key) or {}
            finalstate_info = finalstate_row.get("info") if isinstance(finalstate_row.get("info"), dict) else {}
            finalstate_rel = str(finalstate_row.get("rel") or "")
            key = (mechanical_rel.lower(), visual_rel.lower(), finalstate_rel.lower())
            if key in seen:
                continue
            seen.add(key)
            variant_id = _official_variant_id(mechanical_rel, visual_rel, finalstate_rel)
            compatible_refs = _official_variant_compatiblepart_refs(raw_root, mechanical_rel, finalstate_rel, mechanical_info)
            context_values = _official_variant_context_values(mechanical_info, visual_info, mechanical_rel, visual_rel, finalstate_rel)
            had_official_refs = bool(compatible_refs)
            compatible_refs = _official_contextual_compatiblepart_refs(raw_root, compatible_refs, context_values)
            compatible_source = "official_variant" if compatible_refs else ("none" if had_official_refs else ("actor-static" if actor_static_compatible_refs else "none"))
            if not compatible_refs and not had_official_refs and actor_static_compatible_refs:
                compatible_refs = _official_contextual_compatiblepart_refs(raw_root, actor_static_compatible_refs, context_values)
                compatible_source = "actor-static" if compatible_refs else "none"
            part_assets = _official_variant_part_assets(
                raw_root,
                derived_root,
                car_id,
                variant_id,
                compatible_refs,
                compatible_source=compatible_source,
                force=force,
                uv_size=uv_size,
                run_cache=run_cache,
                asset_progress_cb=asset_progress_cb,
                variant_index=len(rows) + 1,
                variant_total=estimated_variant_total,
            )
            warnings.extend(str(row) for row in part_assets.get("warnings", []) or [])
            rows.append(
                {
                    "id": variant_id,
                    "label": _official_variant_label(mechanical_info, visual_info, mechanical_rel, visual_rel),
                    "source": "official_preview_assets",
                    "mechanicalPreset": mechanical_rel,
                    "mechanicalPresetName": str(mechanical_info.get("name") or ""),
                    "mechanicalPresetShortName": str(mechanical_info.get("short_name") or ""),
                    "visualPreset": visual_rel,
                    "visualPresetName": str(visual_info.get("name") or ""),
                    "visualPresetPresentation": str(visual_info.get("presentation") or ""),
                    "visualPresetExternalCustomizableKnown": bool(visual_info.get("externally_customizable_known")),
                    "visualPresetExternalCustomizable": bool(visual_info.get("externally_customizable")),
                    "visualPresetExternalCompatibilitySource": official_variant_compatibility.COMPATIBILITY_SOURCE,
                    "carFinalState": finalstate_rel,
                    "carData": str(finalstate_info.get("car_data") or ""),
                    "actorPath": str(finalstate_info.get("actor_path") or ""),
                    "compatibleRim": str(finalstate_info.get("compatible_rim") or mechanical_info.get("compatible_rim") or ""),
                    "rimDesign": str(finalstate_info.get("rim_design") or visual_info.get("rim_design") or ""),
                    "cardesign": str(visual_info.get("cardesign") or ""),
                    "startNumber": str(visual_info.get("start_number") or ""),
                    "compatibleParts": compatible_refs,
                    "compatiblePartSource": part_assets.get("compatiblePartSource") or "",
                    "variantPartPreview": part_assets.get("variantPartPreview") or {},
                    "variantPolicy": part_assets.get("variantPolicy") or {},
                    "surfacePolicies": part_assets.get("surfacePolicies") or [],
                    "partsUvTemplate": part_assets.get("partsUvTemplate") or {},
                    "partsUvTemplatePath": part_assets.get("partsUvTemplatePath") or "",
                    "aoPolicy": part_assets.get("aoPolicy") or "base",
                    "variantAoTexture": part_assets.get("variantAoTexture") or {},
                    "aoTexturePath": part_assets.get("aoTexturePath") or "",
                    "aoSourceTexturePath": part_assets.get("aoSourceTexturePath") or "",
                    "aoSourceTexturemipsPath": part_assets.get("aoSourceTexturemipsPath") or "",
                }
            )
            if asset_progress_cb:
                asset_progress_cb("variants", len(rows), estimated_variant_total, f"Prepared variant {len(rows)}")

    if not rows and visual_infos:
        for visual_key, visual_row in sorted(visual_infos.items(), key=lambda item: str(item[1].get("rel") or item[0]).lower()):
            if cancel_cb and cancel_cb():
                raise PackageExtractionCancelled("Official preview asset preparation cancelled")
            visual_rel = str(visual_row.get("rel") or "")
            visual_info = visual_row.get("info") if isinstance(visual_row.get("info"), dict) else {}
            finalstate_row = finalstate_by_visual.get(visual_key) or {}
            finalstate_info = finalstate_row.get("info") if isinstance(finalstate_row.get("info"), dict) else {}
            finalstate_rel = str(finalstate_row.get("rel") or "")
            variant_id = _official_variant_id(visual_rel, finalstate_rel)
            compatible_refs = _official_variant_compatiblepart_refs(raw_root, "", finalstate_rel, {})
            context_values = _official_variant_context_values({}, visual_info, "", visual_rel, finalstate_rel)
            had_official_refs = bool(compatible_refs)
            compatible_refs = _official_contextual_compatiblepart_refs(raw_root, compatible_refs, context_values)
            compatible_source = "official_variant" if compatible_refs else ("none" if had_official_refs else ("actor-static" if actor_static_compatible_refs else "none"))
            if not compatible_refs and not had_official_refs and actor_static_compatible_refs:
                compatible_refs = _official_contextual_compatiblepart_refs(raw_root, actor_static_compatible_refs, context_values)
                compatible_source = "actor-static" if compatible_refs else "none"
            part_assets = _official_variant_part_assets(
                raw_root,
                derived_root,
                car_id,
                variant_id,
                compatible_refs,
                compatible_source=compatible_source,
                force=force,
                uv_size=uv_size,
                run_cache=run_cache,
                asset_progress_cb=asset_progress_cb,
                variant_index=len(rows) + 1,
                variant_total=estimated_variant_total,
            )
            warnings.extend(str(row) for row in part_assets.get("warnings", []) or [])
            rows.append(
                {
                    "id": variant_id,
                    "label": _official_variant_label({}, visual_info, "", visual_rel),
                    "source": "official_preview_assets",
                    "mechanicalPreset": "",
                    "mechanicalPresetName": "",
                    "mechanicalPresetShortName": "",
                    "visualPreset": visual_rel,
                    "visualPresetName": str(visual_info.get("name") or ""),
                    "visualPresetPresentation": str(visual_info.get("presentation") or ""),
                    "visualPresetExternalCustomizableKnown": bool(visual_info.get("externally_customizable_known")),
                    "visualPresetExternalCustomizable": bool(visual_info.get("externally_customizable")),
                    "visualPresetExternalCompatibilitySource": official_variant_compatibility.COMPATIBILITY_SOURCE,
                    "carFinalState": finalstate_rel,
                    "carData": str(finalstate_info.get("car_data") or ""),
                    "actorPath": str(finalstate_info.get("actor_path") or ""),
                    "compatibleRim": str(finalstate_info.get("compatible_rim") or ""),
                    "rimDesign": str(visual_info.get("rim_design") or ""),
                    "cardesign": str(visual_info.get("cardesign") or ""),
                    "startNumber": str(visual_info.get("start_number") or ""),
                    "compatibleParts": compatible_refs,
                    "compatiblePartSource": part_assets.get("compatiblePartSource") or "",
                    "variantPartPreview": part_assets.get("variantPartPreview") or {},
                    "variantPolicy": part_assets.get("variantPolicy") or {},
                    "surfacePolicies": part_assets.get("surfacePolicies") or [],
                    "partsUvTemplate": part_assets.get("partsUvTemplate") or {},
                    "partsUvTemplatePath": part_assets.get("partsUvTemplatePath") or "",
                    "aoPolicy": part_assets.get("aoPolicy") or "base",
                    "variantAoTexture": part_assets.get("variantAoTexture") or {},
                    "aoTexturePath": part_assets.get("aoTexturePath") or "",
                    "aoSourceTexturePath": part_assets.get("aoSourceTexturePath") or "",
                    "aoSourceTexturemipsPath": part_assets.get("aoSourceTexturemipsPath") or "",
                }
            )
            if asset_progress_cb:
                asset_progress_cb("variants", len(rows), estimated_variant_total, f"Prepared variant {len(rows)}")

    warnings.extend(_promote_official_root_parts_uv_template(rows, derived_root))
    rows = official_variant_parts.annotate_and_dedupe_livery_variants(rows)
    saved_car_sources = official_saved_car_generation.preserve_variant_sources(
        rows,
        raw_root,
        derived_root,
        car_id,
        force=force,
    )
    warnings.extend(str(row) for row in saved_car_sources.get("warnings", []) or [])
    rows.sort(key=lambda row: (str(row.get("displayLabel") or row.get("label") or "").lower(), str(row.get("mechanicalPreset") or "").lower(), str(row.get("visualPreset") or "").lower()))
    if asset_progress_cb:
        asset_progress_cb("variants", len(rows), max(1, len(rows)), "Vehicle variants prepared")
    return {
        "vehicleVariants": rows,
        "vehicleVariantCount": len(rows),
        "savedCarSourceCount": int(saved_car_sources.get("count") or 0),
        "defaultVehicleVariantId": official_variant_parts.default_variant_id(rows),
        "warnings": warnings[:50],
    }


def prepare_official_preview_assets(
    car_id: str,
    force: bool = False,
    uv_size: int = 4096,
    *,
    raw_preview_assets_root: str | Path | None = None,
    run_cache: official_preview_pipeline.PreviewRunCache | None = None,
    asset_progress_cb: Any | None = None,
    cancel_cb: Any | None = None,
) -> dict[str, Any]:
    clean_car_id = official_car_resources.safe_car_id(car_id, "")
    if not clean_car_id:
        raise ValueError("Car ID is required")

    def check_cancel() -> None:
        if cancel_cb and cancel_cb():
            raise PackageExtractionCancelled("Official preview asset preparation cancelled")

    check_cancel()
    preview_root = get_official_preview_assets_root(create=True)
    raw_preview_root = Path(raw_preview_assets_root) if raw_preview_assets_root is not None else preview_root
    raw_root = official_car_resources.raw_root(raw_preview_root)
    raw_car_root = official_car_resources.raw_car_root(raw_preview_root, clean_car_id)
    if not raw_car_root.exists() or not raw_car_root.is_dir():
        raise FileNotFoundError(f"Official raw car resources not found: {raw_car_root}")
    mesh_dir = raw_car_root / "meshes"
    mesh_candidates = sorted(mesh_dir.glob("*_exterior.mesh"), key=lambda row: row.name.lower()) if mesh_dir.exists() else []
    if not mesh_candidates:
        raise FileNotFoundError(f"No *_exterior.mesh found for official car resources: {clean_car_id}")
    mesh_path = mesh_candidates[0]
    source_stat = mesh_path.stat()
    texture_size_hints = texture_descriptors.build_texture_size_hints(raw_car_root, root=raw_root)
    derived_root = official_car_resources.derived_car_root(preview_root, clean_car_id, create=True)
    glb_path = derived_root / "exterior.glb"
    uv_path = derived_root / "EXT_SKIN_UV.png"
    ao_png_path = derived_root / preview3d.LIVE_AO_TEXTURE_NAME
    ao_source_texture = _find_official_raw_ao_texture(raw_car_root)
    ao_source_mips = ao_source_texture.with_suffix(".texturemips") if ao_source_texture else None
    ao_source_ready = bool(ao_source_texture and ao_source_mips and ao_source_mips.exists())
    thumbnail_png_path = derived_root / official_car_resources.THUMBNAIL_PNG_FILENAME
    thumbnail_source_texture = _find_official_raw_thumbnail_texture(raw_car_root)
    thumbnail_source_mips = thumbnail_source_texture.with_suffix(".texturemips") if thumbnail_source_texture else None
    thumbnail_source_ready = bool(thumbnail_source_texture and thumbnail_source_mips and thumbnail_source_mips.exists())
    vehicle_variant_data = _official_preview_vehicle_variants(
        raw_root,
        raw_car_root,
        derived_root,
        clean_car_id,
        force=force,
        uv_size=uv_size,
        run_cache=run_cache,
        asset_progress_cb=asset_progress_cb,
        cancel_cb=cancel_cb,
    )
    check_cancel()
    if asset_progress_cb:
        asset_progress_cb("ao", 0, 1, "Preparing variant ambient occlusion textures")
    variant_ao_warnings = _prepare_official_variant_ao_textures(vehicle_variant_data, force=force)
    manifest_path = official_car_resources.derived_manifest_path(preview_root, clean_car_id)
    cached = False
    manifest: dict[str, Any] = {}
    if manifest_path.exists() and not force:
        try:
            manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
        except Exception:
            manifest = {}
        cached = bool(
            manifest.get("ready") is True
            and glb_path.exists()
            and uv_path.exists()
            and str(manifest.get("sourceMeshPath") or "") == str(mesh_path)
            and int(manifest.get("sourceMeshSize") or -1) == int(source_stat.st_size)
            and int(manifest.get("sourceMeshMtimeMs") or -1) == int(source_stat.st_mtime * 1000)
            and int(manifest.get("glbExporterVersion") or 0) == int(preview3d.GLB_EXPORTER_VERSION)
            and int(manifest.get("uvSize") or 0) == int(uv_size)
            and int(manifest.get("previewPipelineVersion") or 0) == official_preview_pipeline.PIPELINE_VERSION
        )
        if cached and ao_source_ready:
            ao_source_mips_stat = ao_source_mips.stat()
            cached = bool(
                ao_png_path.exists()
                and str(manifest.get("aoSourceTexturePath") or "") == str(ao_source_texture)
                and str(manifest.get("aoSourceTexturemipsPath") or "") == str(ao_source_mips)
                and int(manifest.get("aoSourceTexturemipsSize") or -1) == int(ao_source_mips_stat.st_size)
                and int(manifest.get("aoSourceTexturemipsMtimeMs") or -1) == int(ao_source_mips_stat.st_mtime * 1000)
            )
        if cached and thumbnail_source_ready:
            thumbnail_source_mips_stat = thumbnail_source_mips.stat()
            cached = bool(
                thumbnail_png_path.exists()
                and str(manifest.get("thumbnailSourceTexturePath") or "") == str(thumbnail_source_texture)
                and str(manifest.get("thumbnailSourceTexturemipsPath") or "") == str(thumbnail_source_mips)
                and int(manifest.get("thumbnailSourceTexturemipsSize") or -1) == int(thumbnail_source_mips_stat.st_size)
                and int(manifest.get("thumbnailSourceTexturemipsMtimeMs") or -1) == int(thumbnail_source_mips_stat.st_mtime * 1000)
            )
    if cached:
        manifest_updates: dict[str, Any] = {}
        manifest_glb = manifest.get("glb") if isinstance(manifest.get("glb"), dict) else {}
        _apply_official_hidden_component_materials(vehicle_variant_data, manifest_glb)
        if manifest.get("textureSizeHints") != texture_size_hints:
            manifest_updates["textureSizeHints"] = texture_size_hints
        for key in ("vehicleVariants", "vehicleVariantCount", "savedCarSourceCount", "defaultVehicleVariantId"):
            if manifest.get(key) != vehicle_variant_data.get(key):
                manifest_updates[key] = vehicle_variant_data.get(key)
        if vehicle_variant_data.get("warnings") or variant_ao_warnings:
            existing_warnings = [str(row) for row in manifest.get("warnings", []) or []]
            merged_warnings = existing_warnings[:]
            for warning in [*(vehicle_variant_data.get("warnings", []) or []), *variant_ao_warnings]:
                if str(warning) not in merged_warnings:
                    merged_warnings.append(str(warning))
            if merged_warnings != existing_warnings:
                manifest_updates["warnings"] = merged_warnings
        if manifest_updates:
            manifest.update(manifest_updates)
            manifest["textureSizeHints"] = texture_size_hints
            manifest["updatedAt"] = datetime.now().isoformat(timespec="seconds")
            manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8")
        return {
            "ready": True,
            "cached": True,
            "carId": clean_car_id,
            "root": str(derived_root),
            "sourceMeshPath": str(mesh_path),
            "glbPath": str(glb_path),
            "uvTemplatePath": str(uv_path),
            "thumbnailPath": str(thumbnail_png_path) if thumbnail_png_path.exists() else "",
            "manifestPath": str(manifest_path),
            "manifest": manifest,
            "summary": f"Official preview assets already prepared: {clean_car_id}",
        }

    check_cancel()
    if asset_progress_cb:
        asset_progress_cb("mesh", 0, 1, "Generating exterior 3D model")
    mesh_info = _official_preview_glb_mesh_info(mesh_path, raw_root, run_cache=run_cache)
    uv_mesh = _official_cached_mesh_uv(run_cache, mesh_path, raw_root)
    glb = preview3d.write_mesh_info_glb(mesh_info, glb_path, str(mesh_info.get("material_name") or "EXT_SKIN"))
    _apply_official_hidden_component_materials(vehicle_variant_data, glb)
    if asset_progress_cb:
        asset_progress_cb("mesh", 1, 1, "Exterior 3D model generated")
        asset_progress_cb("uv", 0, 1, "Rendering exterior UV template")
    uv = uv_templates.write_ext_skin_uv_template(uv_mesh, uv_path, size=int(uv_size), overwrite=True)
    if asset_progress_cb:
        asset_progress_cb("uv", 1, 1, "Exterior UV template rendered")
    warnings: list[str] = []
    ao_texture: dict[str, Any] = {
        "name": preview3d.LIVE_AO_TEXTURE_NAME,
        "path": str(ao_png_path),
        "exists": ao_png_path.exists(),
        "sourceTexturePath": str(ao_source_texture) if ao_source_texture else "",
        "sourceTexturemipsPath": str(ao_source_mips) if ao_source_mips else "",
        "sourceTextureExists": bool(ao_source_texture and ao_source_texture.exists()),
        "sourceTexturemipsExists": bool(ao_source_mips and ao_source_mips.exists()),
        "converted": False,
        "status": "missing_source",
        "reason": "Official AO source texture not found in Raw cache",
    }
    if ao_source_texture and not ao_source_ready:
        ao_texture["status"] = "missing_mips"
        ao_texture["reason"] = "Official AO texturemips not found in Raw cache"
        warnings.append(f"Official AO texturemips not found: {ao_source_texture.with_suffix('.texturemips')}")
    elif ao_source_ready:
        check_cancel()
        if asset_progress_cb:
            asset_progress_cb("ao", 0, 1, "Converting ambient occlusion texture")
        converted_ao = convert_texture_to_png(ao_source_texture, ao_png_path, overwrite=True, create_dds=False)
        ao_texture.update(
            {
                "exists": ao_png_path.exists(),
                "converted": not bool(converted_ao.get("skipped")),
                "conversion": converted_ao,
                "status": "skipped" if converted_ao.get("skipped") else "generated",
                "reason": converted_ao.get("reason") or "",
            }
        )
    else:
        warnings.append(f"Official AO source texture not found for {clean_car_id}. 3D preview will run without AO.")
    thumbnail: dict[str, Any] = {
        "name": official_car_resources.THUMBNAIL_PNG_FILENAME,
        "path": str(thumbnail_png_path),
        "exists": thumbnail_png_path.exists(),
        "sourceTexturePath": str(thumbnail_source_texture) if thumbnail_source_texture else "",
        "sourceTexturemipsPath": str(thumbnail_source_mips) if thumbnail_source_mips else "",
        "sourceTextureExists": bool(thumbnail_source_texture and thumbnail_source_texture.exists()),
        "sourceTexturemipsExists": bool(thumbnail_source_mips and thumbnail_source_mips.exists()),
        "converted": False,
        "status": "missing_source",
        "reason": "Official generated thumbnail not found in Raw cache",
    }
    if thumbnail_source_texture and not thumbnail_source_ready:
        thumbnail["status"] = "missing_mips"
        thumbnail["reason"] = "Official generated thumbnail texturemips not found in Raw cache"
        warnings.append(f"Official generated thumbnail texturemips not found: {thumbnail_source_texture.with_suffix('.texturemips')}")
    elif thumbnail_source_ready:
        try:
            check_cancel()
            if asset_progress_cb:
                asset_progress_cb("thumbnail", 0, 1, "Converting car thumbnail")
            converted_thumbnail = convert_texture_to_png(thumbnail_source_texture, thumbnail_png_path, overwrite=True, create_dds=False)
            thumbnail.update(
                {
                    "exists": thumbnail_png_path.exists(),
                    "converted": not bool(converted_thumbnail.get("skipped")),
                    "conversion": converted_thumbnail,
                    "status": "skipped" if converted_thumbnail.get("skipped") else "generated",
                    "reason": converted_thumbnail.get("reason") or "",
                }
            )
        except Exception as exc:
            thumbnail["status"] = "conversion_failed"
            thumbnail["reason"] = str(exc)
            warnings.append(f"Official generated thumbnail conversion failed for {clean_car_id}: {exc}")
    else:
        warnings.append(f"Official generated thumbnail not found for {clean_car_id}. Creator will show a placeholder.")
    warnings.extend(str(row) for row in vehicle_variant_data.get("warnings", []) or [])
    warnings.extend(str(row) for row in variant_ao_warnings)
    ready = bool(glb_path.exists() and uv_path.exists() and not uv.get("skipped"))
    if asset_progress_cb:
        asset_progress_cb("thumbnail", 1, 1, "Car thumbnail prepared")
        asset_progress_cb("manifest", 0, 1, "Writing preview manifest")
    manifest = {
        "schemaVersion": 1,
        "previewPipelineVersion": official_preview_pipeline.PIPELINE_VERSION,
        "ready": ready,
        "workflow": "official_external_livery",
        "carId": clean_car_id,
        "createdAt": datetime.now().isoformat(timespec="seconds"),
        "sourceMeshPath": str(mesh_path),
        "sourceMeshRelPath": norm_slashes(str(mesh_path.relative_to(raw_root))),
        "sourceMeshSize": int(source_stat.st_size),
        "sourceMeshMtimeMs": int(source_stat.st_mtime * 1000),
        "rawRoot": str(raw_root),
        "derivedRoot": str(derived_root),
        "glbPath": str(glb_path),
        "uvTemplatePath": str(uv_path),
        "aoTexturePath": str(ao_png_path) if ao_png_path.exists() else "",
        "aoSourceTexturePath": str(ao_source_texture) if ao_source_texture else "",
        "aoSourceTexturemipsPath": str(ao_source_mips) if ao_source_mips else "",
        "aoSourceTexturemipsSize": int(ao_source_mips.stat().st_size) if ao_source_ready and ao_source_mips else None,
        "aoSourceTexturemipsMtimeMs": int(ao_source_mips.stat().st_mtime * 1000) if ao_source_ready and ao_source_mips else None,
        "thumbnailPath": str(thumbnail_png_path) if thumbnail_png_path.exists() else "",
        "thumbnailSourceTexturePath": str(thumbnail_source_texture) if thumbnail_source_texture else "",
        "thumbnailSourceTexturemipsPath": str(thumbnail_source_mips) if thumbnail_source_mips else "",
        "thumbnailSourceTexturemipsSize": int(thumbnail_source_mips.stat().st_size) if thumbnail_source_ready and thumbnail_source_mips else None,
        "thumbnailSourceTexturemipsMtimeMs": int(thumbnail_source_mips.stat().st_mtime * 1000) if thumbnail_source_ready and thumbnail_source_mips else None,
        "glbExporterVersion": int(preview3d.GLB_EXPORTER_VERSION),
        "uvSize": int(uv_size),
        "mesh": {
            "vertexCount": int(mesh_info.get("vertex_count") or 0),
            "triangleCount": int(mesh_info.get("triangle_count") or 0),
            "uvCount": int(mesh_info.get("uv_count") or 0),
            "materialPath": mesh_info.get("material_path"),
            "materialExists": mesh_info.get("material_exists"),
        },
        "glb": glb,
        "uvTemplate": uv,
        "aoTexture": ao_texture,
        "thumbnail": thumbnail,
        "textureSizeHints": texture_size_hints,
        "vehicleVariants": vehicle_variant_data.get("vehicleVariants", []),
        "vehicleVariantCount": int(vehicle_variant_data.get("vehicleVariantCount") or 0),
        "savedCarSourceCount": int(vehicle_variant_data.get("savedCarSourceCount") or 0),
        "defaultVehicleVariantId": str(vehicle_variant_data.get("defaultVehicleVariantId") or ""),
        "warnings": warnings,
    }
    manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8")
    if asset_progress_cb:
        asset_progress_cb("manifest", 1, 1, "Preview manifest written")
    return {
        "ready": ready,
        "cached": False,
        "carId": clean_car_id,
        "root": str(derived_root),
        "sourceMeshPath": str(mesh_path),
        "glbPath": str(glb_path),
        "uvTemplatePath": str(uv_path),
        "thumbnailPath": str(thumbnail_png_path) if thumbnail_png_path.exists() else "",
        "manifestPath": str(manifest_path),
        "manifest": manifest,
        "mesh": manifest["mesh"],
        "glb": glb,
        "uvTemplate": uv,
        "aoTexture": ao_texture,
        "thumbnail": thumbnail,
        "warnings": warnings,
        "summary": "\n".join(
            [
                f"Official preview assets prepared: {clean_car_id}",
                f"Source mesh: {mesh_path}",
                f"GLB: {glb_path}",
                f"UV template: {uv_path}",
                f"AO texture: {ao_png_path if ao_png_path.exists() else '-'}",
                f"Thumbnail: {thumbnail_png_path if thumbnail_png_path.exists() else '-'}",
                f"Triangles: {mesh_info.get('triangle_count') or 0}",
                f"UV triangles drawn: {uv.get('drawnTriangleCount') or 0}",
            ]
        ),
    }


SUPPORTED_CAR_LOCALES = ("cn", "de", "en", "es", "fr", "it")


def make_visual_loc_key(car_content_path: str, display_name: str) -> str:
    model_name = split_content_path(car_content_path)[-1]
    normalized = re.sub(r"[^0-9a-z]+", "_", str(display_name or "").lower()).strip("_")
    return f"{model_name}_{normalized}" if normalized else model_name


def iter_car_loc_files(root: Path, languages: tuple[str, ...] = SUPPORTED_CAR_LOCALES) -> list[Path]:
    root_resolved = root.resolve()
    candidates: list[Path] = [root_resolved / "uiresources" / "localization"]
    if root_resolved.name.lower() == "content":
        candidates.append(root_resolved.parent / "uiresources" / "localization")
    base = next((candidate.resolve() for candidate in candidates if candidate.exists() and candidate.is_dir()), candidates[0])
    files: list[Path] = []
    for language in languages:
        for name in (f"{language}.cars.loc", f"{language}.cars.release.loc"):
            candidate = base / name
            if candidate.exists() and candidate.is_file():
                files.append(candidate)
    return files


def upsert_loc_entry(path: Path, key: str, value: str, writer: GameFileWriter | None = None) -> tuple[bool, Path | None]:
    text = path.read_text(encoding="utf-8", errors="replace") if path.exists() else ""
    lines = text.splitlines()
    new_line = f"{key}={value}"
    changed = False
    found = False
    for index, line in enumerate(lines):
        if line.startswith(f"{key}="):
            found = True
            if line != new_line:
                lines[index] = new_line
                changed = True
            break
    if not found:
        lines.append(new_line)
        changed = True
    if not changed:
        return False, None
    newline = "\r\n" if "\r\n" in text else "\n"
    out_text = newline.join(lines)
    if text.endswith(("\r\n", "\n")) or not text:
        out_text += newline
    if writer:
        result = writer.write_bytes(path, out_text.encode("utf-8"), label="localization entry")
        backup = Path(result.backup) if result.backup else None
    else:
        backup = safe_write_text(path, out_text)
    return True, backup


def remove_loc_entry(path: Path, key: str, writer: GameFileWriter | None = None) -> tuple[bool, Path | None]:
    if not path.exists():
        return False, None
    text = path.read_text(encoding="utf-8", errors="replace")
    lines = text.splitlines()
    kept = [line for line in lines if not line.startswith(f"{key}=")]
    if len(kept) == len(lines):
        return False, None
    newline = "\r\n" if "\r\n" in text else "\n"
    out_text = newline.join(kept)
    if kept and text.endswith(("\r\n", "\n")):
        out_text += newline
    if writer:
        result = writer.write_bytes(path, out_text.encode("utf-8"), label="remove localization entry")
        backup = Path(result.backup) if result.backup else None
    else:
        backup = safe_write_text(path, out_text)
    return True, backup


def write_visual_loc_entries(root: Path, car_content_path: str, display_name: str, languages: tuple[str, ...] = SUPPORTED_CAR_LOCALES, writer: GameFileWriter | None = None) -> tuple[str, list[dict[str, Any]]]:
    loc_key = make_visual_loc_key(car_content_path, display_name)
    updates: list[dict[str, Any]] = []
    for loc_path in iter_car_loc_files(root, languages):
        changed, backup = upsert_loc_entry(loc_path, loc_key, display_name, writer=writer)
        updates.append({"path": loc_path, "changed": changed, "backup": backup})
    return loc_key, updates


def make_generated_thumbnail_name(car_content_path: str, mechanical_rel: str, visual_rel: str) -> str:
    car_model = split_content_path(car_content_path)[-1]
    mech_stem = Path(norm_slashes(mechanical_rel).replace("\\", "/")).stem
    visual_stem = Path(norm_slashes(visual_rel).replace("\\", "/")).stem
    return f"{car_model}-{mech_stem}-{visual_stem}.texture"


def generated_thumbnail_disk_path(root: Path, car_content_path: str, mechanical_rel: str, visual_rel: str) -> Path:
    car_model = split_content_path(car_content_path)[-1]
    return root / "content" / "cars" / car_model / "generated" / "thumbnails" / make_generated_thumbnail_name(car_content_path, mechanical_rel, visual_rel)


def clone_generated_thumbnail(root: Path, car_content_path: str, source_mechanical_rel: str, source_visual_rel: str, target_mechanical_rel: str, target_visual_rel: str, overwrite: bool = False, writer: GameFileWriter | None = None) -> tuple[Path | None, Path | None, bool]:
    source_texture = generated_thumbnail_disk_path(root, car_content_path, source_mechanical_rel, source_visual_rel)
    source_mips = source_texture.with_suffix(".texturemips")
    if not source_texture.exists() or not source_mips.exists():
        return None, None, False
    target_texture = generated_thumbnail_disk_path(root, car_content_path, target_mechanical_rel, target_visual_rel)
    target_mips = target_texture.with_suffix(".texturemips")
    if (target_texture.exists() or target_mips.exists()) and not overwrite:
        raise FileExistsError(f"Target thumbnail exists: {target_texture}")
    _write_game_bytes(target_texture, source_texture.read_bytes(), writer, label="thumbnail texture", backup=False)
    _write_game_bytes(target_mips, source_mips.read_bytes(), writer, label="thumbnail mips", backup=False)
    return target_texture, target_mips, True


def build_generated_thumbnail_from_preview(root: Path, car_content_path: str, source_mechanical_rel: str, source_visual_rel: str, target_mechanical_rel: str, target_visual_rel: str, preview_image: Path, overwrite: bool = False, writer: GameFileWriter | None = None) -> tuple[Path | None, Path | None, bool]:
    source_texture = generated_thumbnail_disk_path(root, car_content_path, source_mechanical_rel, source_visual_rel)
    if not source_texture.exists():
        raise FileNotFoundError(f"Source thumbnail template not found: {source_texture}")

    target_texture = generated_thumbnail_disk_path(root, car_content_path, target_mechanical_rel, target_visual_rel)
    target_mips = target_texture.with_suffix(".texturemips")
    if (target_texture.exists() or target_mips.exists()) and not overwrite:
        raise FileExistsError(f"Target thumbnail exists: {target_texture}")

    preview_info = inspect_preview_image(preview_image)
    if not preview_info.get("valid"):
        raise RuntimeError(f"Preview image must be 1024x1024, got {preview_info.get('width')}x{preview_info.get('height')}")
    with tempfile.NamedTemporaryFile(suffix=".dds", delete=False) as handle:
        tmp_dds = Path(handle.name)
    try:
        write_preview_image_as_matching_dds(preview_image, tmp_dds, source_texture)
        tiled = texturemips_bytes_from_dds(source_texture, tmp_dds)
        _write_game_bytes(target_texture, source_texture.read_bytes(), writer, label="thumbnail texture", backup=False)
        _write_game_bytes(target_mips, tiled, writer, label="thumbnail mips", backup=False)
    finally:
        try:
            tmp_dds.unlink(missing_ok=True)
        except Exception:
            pass
    return target_texture, target_mips, True


def write_generated_thumbnail_from_preview(template_texture: Path, target_texture: Path, preview_image: Path, overwrite: bool = True, writer: GameFileWriter | None = None) -> tuple[Path, Path]:
    if not template_texture.exists():
        raise FileNotFoundError(f"Thumbnail template not found: {template_texture}")
    target_mips = target_texture.with_suffix(".texturemips")
    if (target_texture.exists() or target_mips.exists()) and not overwrite:
        raise FileExistsError(f"Target thumbnail exists: {target_texture}")
    preview_info = inspect_preview_image(preview_image)
    if not preview_info.get("valid"):
        raise RuntimeError(f"Preview image must be 1024x1024, got {preview_info.get('width')}x{preview_info.get('height')}")
    with tempfile.NamedTemporaryFile(suffix=".dds", delete=False) as handle:
        tmp_dds = Path(handle.name)
    try:
        write_preview_image_as_matching_dds(preview_image, tmp_dds, template_texture)
        tiled = texturemips_bytes_from_dds(template_texture, tmp_dds)
        _write_game_bytes(target_texture, template_texture.read_bytes(), writer, label="thumbnail texture", backup=False)
        _write_game_bytes(target_mips, tiled, writer, label="thumbnail mips", backup=False)
    finally:
        try:
            tmp_dds.unlink(missing_ok=True)
        except Exception:
            pass
    return target_texture, target_mips


def generated_thumbnail_template_skip_reason(path: Path) -> str:
    if not path.is_file():
        return "template is not a file"
    mips_path = path.with_suffix(".texturemips")
    if not mips_path.exists():
        return "template .texturemips missing"
    try:
        desc = parse_texture_descriptor(path)
        block_bytes = infer_block_bytes_from_tiled_size(mips_path.stat().st_size, desc)
    except Exception as exc:
        return str(exc)
    if block_bytes not in (8, 16):
        return f"template block size could not be inferred from {mips_path.stat().st_size:,} bytes"
    return ""


def is_optional_thumbnail_generation_error(exc: Exception) -> bool:
    message = str(exc)
    optional_markers = (
        "No tiled mip layout field found in .texture",
        "Could not read tile offsets/counts from .texture",
        "Could not read width/height/mip count from .texture",
        "Could not infer BC block size",
        "Custom preview PNG cannot be converted for this thumbnail slot yet",
        "texconv.exe was not found",
    )
    return any(marker in message for marker in optional_markers)


def find_any_generated_thumbnail_template(root: Path, car_content_path: str) -> Path | None:
    car_model = split_content_path(car_content_path)[-1]
    folder = root / "content" / "cars" / car_model / "generated" / "thumbnails"
    if not folder.exists():
        return None
    for path in sorted(folder.glob("*.texture")):
        if not generated_thumbnail_template_skip_reason(path):
            return path
    return None


def inspect_preview_image(path: str | Path) -> dict[str, Any]:
    try:
        from PIL import Image
    except Exception as exc:
        raise RuntimeError(f"Pillow is required for preview validation: {exc}") from exc
    image_path = Path(path)
    if not image_path.exists():
        raise FileNotFoundError(f"Preview image not found: {image_path}")
    with Image.open(image_path) as image:
        width, height = image.size
        return {
            "path": image_path,
            "width": int(width),
            "height": int(height),
            "valid": width == 1024 and height == 1024,
            "format": str(image.format or ""),
        }


def register_my_skin(root: Path, result: "CreateSkinResult", skin_id: str, display_name: str, start_number: str | None = None, local_root: Path | None = None, active: bool = True, author: str = "", description: str = "") -> tuple[dict[str, Any], Path]:
    game_root = normalize_game_root(root)
    car_model = split_content_path(result.info.car_content_path)[-1]
    storage_root = Path(local_root) if local_root else game_root

    def rel_for(path: Path | None) -> str | None:
        if path is None:
            return None
        if local_root:
            return game_rel_from_disk(storage_root, path) or game_rel_from_disk(game_root, path)
        return game_rel_from_disk(game_root, path)

    def rel_list(paths: list[Path] | None) -> list[str]:
        values: list[str] = []
        seen: set[str] = set()
        for path in paths or []:
            rel = rel_for(path)
            if not rel:
                continue
            key = rel.lower()
            if key in seen:
                continue
            seen.add(key)
            values.append(rel)
        return values

    loc_files = [rel_for(Path(str(row.get("path")))) for row in (result.loc_updates or []) if row.get("path")]
    skin_folder_rel = rel_for(result.skin_folder)
    thumbnail_png_disk = _resolve_existing_skin_thumbnail_png(storage_root, skin_folder_rel)
    mechanical_presets = rel_list(result.mechanical_preset_paths)
    carfinalstates = rel_list(result.carfinalstate_paths)
    thumbnails = rel_list(result.thumbnail_paths)
    thumbnail_mips_list = rel_list(result.thumbnail_mips_paths)
    entry: dict[str, Any] = {
        "key": f"{car_model}:{skin_id}".lower(),
        "skin_id": skin_id,
        "display_name": display_name,
        "car_model": car_model,
        "car_content_path": result.info.car_content_path,
        "game_root": str(game_root),
        "local_root": str(storage_root) if local_root else "",
        "created_at": datetime.now().isoformat(timespec="seconds"),
        "origin": skin_policy.ORIGIN_CREATED,
        "project_type": project_types.PROJECT_TYPE_ADVANCED_EXTRACTED_CONTENT,
        "project_state": skin_policy.PROJECT_STATE_EDITABLE,
        "managed_by": MY_SKIN_MANAGED_BY_LIVERYLAB,
        "liverylab_managed": True,
        "identity_locked": True,
        "start_number": start_number or "",
        "visual_loc_key": result.visual_loc_key or "",
        "active": bool(active),
        "files": {
            "cardesign": rel_for(result.cardesign_path),
            "design": rel_for(result.design_path),
            "skin_folder": skin_folder_rel,
            "material": rel_for(result.material_path),
            "actor": rel_for(result.actor_path),
            "visual_preset": rel_for(result.visual_preset_path),
            "mechanical_preset": rel_for(result.mechanical_preset_path),
            "carfinalstate": rel_for(result.carfinalstate_path),
            "thumbnail": rel_for(result.thumbnail_path),
            "thumbnail_mips": rel_for(result.thumbnail_mips_path),
            "thumbnail_png": rel_for(thumbnail_png_disk),
        },
        "loc_files": [value for value in loc_files if value],
    }
    metadata = {
        "author": str(author or "").strip(),
        "description": str(description or "").strip()[:96].strip(),
    }
    metadata = {key: value for key, value in metadata.items() if value}
    if metadata:
        entry["metadata"] = metadata
    if mechanical_presets:
        entry["files"]["mechanical_presets"] = mechanical_presets
        entry["files"]["mechanical_preset"] = entry["files"].get("mechanical_preset") or mechanical_presets[0]
    if carfinalstates:
        entry["files"]["carfinalstates"] = carfinalstates
        entry["files"]["carfinalstate"] = entry["files"].get("carfinalstate") or carfinalstates[0]
    if thumbnails:
        entry["files"]["thumbnails"] = thumbnails
        entry["files"]["thumbnail"] = entry["files"].get("thumbnail") or thumbnails[0]
    if thumbnail_mips_list:
        entry["files"]["thumbnail_mips_list"] = thumbnail_mips_list
        entry["files"]["thumbnail_mips"] = entry["files"].get("thumbnail_mips") or thumbnail_mips_list[0]
    if result.source_skin:
        entry["source_skin"] = _json_ready(result.source_skin)
    if result.numberplate_variant:
        entry["numberplate_variant"] = _json_ready(result.numberplate_variant.get("variant") or result.numberplate_variant)
    skins = load_my_skins()
    replaced = False
    for index, item in enumerate(skins):
        if str(item.get("key", "")).lower() == entry["key"]:
            skins[index] = entry
            replaced = True
            break
    if not replaced:
        skins.append(entry)
    saved = save_my_skins(skins)
    return entry, saved


def _is_standard_skin_stem(stem: str) -> bool:
    value = str(stem or "").strip().lower()
    return bool(
        re.fullmatch(r"preset_[a-z0-9_]+_visual_\d+", value)
        or re.fullmatch(r"ks_[a-z0-9_]+_visual_\d+", value)
        or re.fullmatch(r"visual_\d+", value)
    )


def _is_standard_cardesign_stem(stem: str) -> bool:
    value = str(stem or "").strip().lower()
    return bool(re.fullmatch(r"design_[a-z0-9_]+_\d+", value))


def _my_skin_path_stem(entry: dict[str, Any], file_key: str) -> str:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    rel = str(files.get(file_key) or "").strip()
    if not rel:
        return ""
    return Path(rel.replace("\\", "/")).stem


def _is_standard_my_skin_entry(entry: dict[str, Any]) -> bool:
    cardesign_stem = _my_skin_path_stem(entry, "cardesign") or str(entry.get("skin_id") or "").strip()
    visual_stem = _my_skin_path_stem(entry, "visual_preset")
    if not _is_standard_cardesign_stem(cardesign_stem):
        return False
    return not visual_stem or _is_standard_skin_stem(visual_stem)


def _my_skin_has_liverylab_local_source(entry: dict[str, Any]) -> bool:
    raw_root = str(entry.get("local_root") or "").strip()
    if raw_root:
        return True
    direct = _entry_direct_local_skin_root(entry)
    return bool(direct and direct.exists() and direct.is_dir())


def _is_liverylab_managed_my_skin(entry: dict[str, Any]) -> bool:
    managed_by = str(entry.get("managed_by") or "").strip().lower()
    if managed_by:
        return managed_by == MY_SKIN_MANAGED_BY_LIVERYLAB
    if entry.get("liverylab_managed") is True:
        return True
    if _my_skin_has_liverylab_local_source(entry):
        return True
    return not _is_standard_my_skin_entry(entry)


def migrate_my_skin_management_flags(items: list[dict[str, Any]] | None = None, save: bool = True) -> dict[str, Any]:
    skins = items if items is not None else _read_my_skins_registry(get_my_skins_registry_path())
    changed = False
    marked_liverylab = 0
    marked_external = 0
    normalized_project_types = 0
    for entry in skins:
        normalized_project_type = project_types.project_type_for_entry(entry)
        if entry.get("project_type") != normalized_project_type:
            entry["project_type"] = normalized_project_type
            normalized_project_types += 1
            changed = True
        managed_by = str(entry.get("managed_by") or "").strip().lower()
        if managed_by == MY_SKIN_MANAGED_BY_LIVERYLAB:
            if entry.get("liverylab_managed") is not True:
                entry["liverylab_managed"] = True
                changed = True
            continue
        if managed_by:
            next_flag = managed_by == MY_SKIN_MANAGED_BY_LIVERYLAB
            if entry.get("liverylab_managed") != next_flag:
                entry["liverylab_managed"] = next_flag
                changed = True
            continue
        if _is_liverylab_managed_my_skin(entry):
            entry["managed_by"] = MY_SKIN_MANAGED_BY_LIVERYLAB
            entry["liverylab_managed"] = True
            marked_liverylab += 1
        else:
            entry["managed_by"] = MY_SKIN_MANAGED_BY_GAME_IMPORT
            entry["liverylab_managed"] = False
            marked_external += 1
        changed = True
    saved_path = save_my_skins(skins) if changed and save else None
    return {
        "changed": changed,
        "markedLiveryLab": marked_liverylab,
        "markedExternal": marked_external,
        "normalizedProjectTypes": normalized_project_types,
        "saved": str(saved_path) if saved_path else "",
    }


def import_existing_my_skins(root: Path) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    content_root = game_root / "content" / "cars"
    imported: list[dict[str, Any]] = []
    skipped = 0
    existing = {str(item.get("key", "")).lower() for item in load_my_skins() if isinstance(item, dict)}
    skins = load_my_skins()
    if not content_root.exists():
        return {"imported": [], "imported_count": 0, "skipped_count": 0, "saved": None}

    for visual_path in content_root.rglob("*.visualcarpreset"):
        if "_acevo_backups" in str(visual_path):
            continue
        try:
            info = extract_visualpreset_info(visual_path.read_bytes())
            cardesign_rel = str(info.get("cardesign") or "").strip()
            if not cardesign_rel:
                skipped += 1
                continue
            visual_stem = visual_path.stem
            cardesign_path = resolve_content_path(game_root, cardesign_rel)
            if not cardesign_path.exists():
                skipped += 1
                continue
            cardesign_stem = cardesign_path.stem
            if _is_standard_skin_stem(visual_stem) and _is_standard_cardesign_stem(cardesign_stem):
                skipped += 1
                continue
            design_rel = None
            design_path = None
            material_path = None
            car_content_path = None
            source_skin_folder = None
            try:
                design_rel = extract_cardesign_details(cardesign_path.read_bytes()).get("design_path")
                if design_rel:
                    design_path = resolve_content_path(game_root, str(design_rel))
                if design_path and design_path.exists():
                    design_info = extract_design_info(cardesign_path.read_bytes(), design_path.read_bytes())
                    material_path = find_material_file(game_root, str(design_info.ext_skin_material_path or "")) if design_info.ext_skin_material_path else None
                    car_content_path = design_info.car_content_path
                    source_skin_folder = design_info.source_skin_folder_name
            except Exception:
                pass
            car_content_path = car_content_path or ("\\".join(split_content_path(cardesign_rel)[:3]) if len(split_content_path(cardesign_rel)) >= 3 else "")
            car_model = split_content_path(car_content_path)[-1] if car_content_path else visual_path.parents[1].name
            actor_path = find_actor_file(game_root, car_content_path) if car_content_path else None
            visual_rel = content_rel_from_disk(game_root, visual_path)
            mech_path = find_mechanicalpreset_for_visual(game_root, actor_path, visual_path, visual_rel) if visual_rel else None
            mech_rel = content_rel_from_disk(game_root, mech_path) if mech_path else None
            finalstate_path = find_carfinalstate_for_visual(game_root, visual_rel, mech_rel) if visual_rel else None
            display_name = str(info.get("name") or info.get("presentation") or visual_stem).strip()
            loc_key = make_visual_loc_key(car_content_path or f"content\\cars\\{car_model}", display_name)
            start_number = None
            if finalstate_path and finalstate_path.exists():
                try:
                    start_number = inspect_carfinalstate_readable(finalstate_path, root=game_root).get("start_number")
                except Exception:
                    start_number = None
            if not start_number and material_path:
                start_number = _infer_start_number_from_material_path(str(game_rel_from_disk(game_root, material_path) or material_path))
            thumbnail_path = generated_thumbnail_disk_path(
                game_root,
                car_content_path or f"content\\cars\\{car_model}",
                mech_rel or "",
                visual_rel or "",
            ) if mech_rel and visual_rel else None
            skin_folder_rel = game_rel_from_disk(game_root, game_root / "content" / "cars" / car_model / "skins" / source_skin_folder) if source_skin_folder else None
            thumbnail_png_disk = _resolve_existing_skin_thumbnail_png(game_root, skin_folder_rel)
            entry: dict[str, Any] = {
                "key": f"{car_model}:{cardesign_stem}".lower(),
                "skin_id": cardesign_stem,
                "display_name": display_name,
                "car_model": car_model,
                "car_content_path": car_content_path or f"content\\cars\\{car_model}",
                "game_root": str(game_root),
                "created_at": datetime.now().isoformat(timespec="seconds"),
                "origin": skin_policy.ORIGIN_GAME_IMPORT,
                "project_type": project_types.PROJECT_TYPE_ADVANCED_EXTRACTED_CONTENT,
                "project_state": skin_policy.PROJECT_STATE_NONE,
                "managed_by": MY_SKIN_MANAGED_BY_LIVERYLAB,
                "liverylab_managed": True,
                "start_number": str(start_number or ""),
                "visual_loc_key": loc_key,
                "active": True,
                "files": {
                    "cardesign": game_rel_from_disk(game_root, cardesign_path),
                    "design": game_rel_from_disk(game_root, design_path),
                    "skin_folder": skin_folder_rel,
                    "material": game_rel_from_disk(game_root, material_path),
                    "actor": game_rel_from_disk(game_root, actor_path),
                    "visual_preset": game_rel_from_disk(game_root, visual_path),
                    "mechanical_preset": game_rel_from_disk(game_root, mech_path),
                    "carfinalstate": game_rel_from_disk(game_root, finalstate_path),
                    "thumbnail": game_rel_from_disk(game_root, thumbnail_path),
                    "thumbnail_mips": game_rel_from_disk(game_root, thumbnail_path.with_suffix(".texturemips")) if thumbnail_path else None,
                    "thumbnail_png": game_rel_from_disk(game_root, thumbnail_png_disk),
                },
                "loc_files": [game_rel_from_disk(game_root, row) for row in iter_car_loc_files(game_root)],
            }
            if entry["key"] in existing:
                skipped += 1
                continue
            existing.add(entry["key"])
            skins.append(entry)
            imported.append(entry)
        except Exception:
            skipped += 1
            continue
    saved = save_my_skins(skins) if imported else None
    safety_refresh = {"attempted": False, "status": "skipped", "reason": "local library import only"} if imported else {"attempted": False, "status": "skipped", "reason": "no skins imported"}
    return {"imported": imported, "imported_count": len(imported), "skipped_count": skipped, "saved": saved, "safetyRefresh": safety_refresh}


def _merge_stored_numberplate_variant_metadata(stored: dict[str, Any], inspected: dict[str, Any]) -> dict[str, Any]:
    result = dict(inspected or {})
    if stored.get("template_id"):
        result["template_id"] = str(stored.get("template_id"))
    for key in (
        "placement_sources",
        "created_by",
        "owns_actor_numberplate",
        "published_template_font_path",
        "game_text_fields_by_surface",
        "source_type",
        "numberplate_source",
        "numberplate_hidden",
    ):
        value = stored.get(key)
        if value in (None, "", [], {}):
            continue
        if key not in result or result.get(key) in (None, "", [], {}):
            result[key] = _json_ready(value)
    return result


def _my_skin_numberplate_support(entry: dict[str, Any], game_root: Path | None) -> dict[str, Any]:
    fallback_message = "This car does not support numberplates yet"
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    if not source_skin:
        return {
            "supported": True,
            "known": False,
            "reason": "Source skin metadata is not available.",
            "message": "",
            "source": "unknown",
            "surfaces": [],
        }
    if game_root is None:
        return {
            "supported": True,
            "known": False,
            "reason": "Game root is not available.",
            "message": "",
            "source": "unknown",
            "surfaces": [],
        }
    source_actor_rel = str(source_skin.get("actor") or "").strip()
    source_actor_path = _disk_from_game_rel(game_root, source_actor_rel) if source_actor_rel else None
    if not source_actor_path or not source_actor_path.exists():
        return {
            "supported": True,
            "known": False,
            "reason": "Source actor file is not available.",
            "message": "",
            "source": "unknown",
            "surfaces": [],
        }
    try:
        actor_data = source_actor_path.read_bytes()
        numberplates = _actor_extract_numberplates(actor_data)
        placements = _actor_extract_numberplate_placements(actor_data)
        usable_surfaces: set[int] = set()
        for placement in placements:
            try:
                surface = int(placement.get("outer_field_number") or placement.get("surface") or 0)
                numberplate_index = int(placement.get("field_2") or placement.get("numberplate_index") or 0)
            except Exception:
                continue
            if surface in {1, 3, 4} and numberplate_index > 0:
                usable_surfaces.add(surface)
        selector, selector_source = _source_numberplate_selector(game_root, source_skin)
        supported = bool(numberplates and usable_surfaces)
        return {
            "supported": supported,
            "known": True,
            "reason": "" if supported else fallback_message,
            "message": "" if supported else fallback_message,
            "source": str(source_actor_path),
            "sourceActor": source_actor_rel,
            "selectorSource": selector_source,
            "selector": _json_ready(selector),
            "numberplateCount": len(numberplates),
            "placementCount": len(placements),
            "surfaces": sorted(usable_surfaces),
            "hasHood": 1 in usable_surfaces,
            "hasDoor": bool({3, 4} & usable_surfaces),
        }
    except Exception as exc:
        return {
            "supported": True,
            "known": False,
            "reason": f"Numberplate support could not be inspected: {exc}",
            "message": "",
            "source": "unknown",
            "surfaces": [],
        }


def list_my_skins(
    root: Path | None = None,
    include_hidden: bool = False,
    scope: str = workflow_scopes.SCOPE_ALL,
    *,
    items_override: list[dict[str, Any]] | None = None,
) -> list[dict[str, Any]]:
    game_root = normalize_game_root(root) if root else None
    clean_scope = workflow_scopes.normalize_scope(scope)
    items = [dict(item) for item in items_override] if isinstance(items_override, list) else load_my_skins()
    rows: list[dict[str, Any]] = []
    savedcars_index = _build_savedcars_scan_index()
    for item in items:
        if not _is_liverylab_managed_my_skin(item):
            continue
        if not workflow_scopes.entry_matches_scope(item, clean_scope):
            continue
        hidden = bool(item.get("hidden"))
        active = bool(item.get("active", True))
        if hidden and not include_hidden:
            continue
        item_project_type = project_types.project_type_for_entry(item)
        if item_project_type == project_types.PROJECT_TYPE_ADVANCED_EXTRACTED_CONTENT:
            try:
                item_root = _resolve_my_skin_game_root(item, preferred_root=game_root)
            except FileNotFoundError:
                item_root = None
        else:
            item_root = normalize_game_root(str(item.get("game_root", ""))) if item.get("game_root") else None
        if game_root and item_root and item_root != game_root:
            continue
        local_root_raw = str(item.get("local_root") or "").strip()
        local_root = _entry_local_root(item)
        if local_root and str(local_root) != local_root_raw:
            item["local_root"] = str(local_root)
        resolve_roots = [root for root in (local_root, item_root) if root]

        def resolve_skin_file(rel_path: str | None) -> Path | None:
            rel_norm = normalize_skin_asset_rel_path(str(rel_path or ""))
            if not rel_norm:
                return None
            candidates = [root / Path(*split_content_path(rel_norm)) for root in resolve_roots]
            for candidate in candidates:
                if candidate.exists():
                    return candidate
            return candidates[0] if candidates else None

        files = item.get("files", {})
        file_rows: list[dict[str, Any]] = []
        present = 0
        missing = 0
        normalized_files: dict[str, Any] = {}
        normalized_changed = False
        for kind, rel in files.items():
            if not rel:
                continue
            rel_norm = normalize_skin_asset_rel_path(str(rel))
            normalized_files[kind] = rel_norm
            normalized_changed = normalized_changed or (rel_norm != str(rel))
            disk = resolve_skin_file(rel_norm)
            exists = bool(disk and disk.exists())
            if exists:
                present += 1
            else:
                missing += 1
            file_rows.append({
                "kind": kind,
                "path": rel_norm,
                "disk_path": str(disk) if disk else "",
                "exists": exists,
            })
        if normalized_changed:
            merged = dict(files)
            merged.update(normalized_files)
            item["files"] = merged
        file_rows.sort(key=lambda row: (not row["exists"], row["kind"]))
        thumbnail_png_rel = str(files.get("thumbnail_png") or "").strip()
        thumbnail_png_disk = resolve_skin_file(thumbnail_png_rel) if thumbnail_png_rel else None
        if not (thumbnail_png_disk and thumbnail_png_disk.exists()):
            thumbnail_png_disk = _resolve_existing_skin_thumbnail_png(local_root or item_root, str(files.get("skin_folder") or "")) if (local_root or item_root) else None
        project_thumbnail_disk = _project_skin_thumbnail_path_for_entry(item)
        if not project_thumbnail_disk.exists():
            project_thumbnail_disk = None
        ace_snapshot = _ace_savedcar_thumbnail_for_skin_entry(item, item_root, savedcars_index=savedcars_index)
        effective_thumbnail_disk_path = _effective_my_skin_thumbnail_path(item, project_thumbnail_disk, thumbnail_png_disk)
        stored_numberplate_variant = item.get("numberplate_variant") if isinstance(item.get("numberplate_variant"), dict) else {}
        numberplate_variant = dict(stored_numberplate_variant)
        actor_rel = normalize_skin_asset_rel_path(str(files.get("actor") or ""))
        visual_rel = normalize_skin_asset_rel_path(str(files.get("visual_preset") or ""))
        actor_disk = resolve_skin_file(actor_rel)
        visual_disk = resolve_skin_file(visual_rel)
        if actor_rel and visual_rel:
            paired_roots: list[Path] = []
            if active and item_root:
                paired_roots.append(item_root)
            if local_root and local_root not in paired_roots:
                paired_roots.append(local_root)
            if item_root and item_root not in paired_roots:
                paired_roots.append(item_root)
            for candidate_root in paired_roots:
                paired_actor = candidate_root / Path(*split_content_path(actor_rel))
                paired_visual = candidate_root / Path(*split_content_path(visual_rel))
                if paired_actor.exists() and paired_visual.exists():
                    actor_disk = paired_actor
                    visual_disk = paired_visual
                    break
        if actor_disk and visual_disk and actor_disk.exists() and visual_disk.exists():
            try:
                inspected_numberplate_variant = inspect_visual_numberplate_variant(actor_disk, visual_disk)
                numberplate_variant = _merge_stored_numberplate_variant_metadata(stored_numberplate_variant, inspected_numberplate_variant)
            except Exception:
                pass
        source_skin = dict(item.get("source_skin") or {}) if isinstance(item.get("source_skin"), dict) else {}
        if not isinstance(source_skin.get("driver_skin"), dict):
            try:
                inferred_driver_skin = driver_library.infer_driver_metadata_from_skin_entry(item)
            except Exception:
                inferred_driver_skin = {}
            if inferred_driver_skin:
                source_skin["driver_skin"] = inferred_driver_skin
        item_capabilities = project_capabilities.capabilities_for_entry(
            item,
            item_root,
            livery_library_root=get_livery_library_root(),
            official_livery_library_root=get_official_livery_library_root(create=False),
        )
        item_capability_details = project_capabilities.capability_details_for_entry(
            item,
            item_root,
            livery_library_root=get_livery_library_root(),
            official_livery_library_root=get_official_livery_library_root(create=False),
        )
        item["capabilities"] = _json_ready(item_capabilities)
        item["capabilityDetails"] = _json_ready(item_capability_details)
        item_policy = skin_policy.policy_for_skin(item)
        official_external_inspection = official_external_library.inspect_entry(item) if item_project_type == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY else {}
        effective_active = active
        official_deployment_health: dict[str, Any] = {}
        if item_project_type == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY and active:
            try:
                active_folder = _resolve_official_external_active_folder(item)
            except Exception as exc:
                active_folder = None
                folder_error = str(exc)
            else:
                folder_error = ""
            saved_car_path = Path(str(ace_snapshot.get("saved_car_path") or "")).expanduser()
            external_ready = bool(active_folder and (active_folder / official_external_liveries.EXTERNAL_LIVERY_JSON).is_file())
            saved_car_ready = bool(str(ace_snapshot.get("saved_car_path") or "").strip() and saved_car_path.is_file())
            effective_active = bool(external_ready and saved_car_ready)
            official_deployment_health = {
                "ready": effective_active,
                "externalReady": external_ready,
                "externalFolder": str(active_folder) if active_folder else "",
                "savedCarReady": saved_car_ready,
                "savedCarPath": str(saved_car_path) if str(ace_snapshot.get("saved_car_path") or "").strip() else "",
                "reason": "" if effective_active else (folder_error or "Official deployment or Garage SavedCar is missing."),
            }
        official_external_validation = (
            official_external_inspection.get("localValidation")
            if isinstance(official_external_inspection.get("localValidation"), dict)
            else {}
        )
        official_external_preflight = (
            _read_official_external_project_deployment_preflight(local_root)
            if item_project_type == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY
            and official_external_workflow.role_for_entry(item) == official_external_workflow.ROLE_PROJECT
            else {}
        )
        numberplate_support = _my_skin_numberplate_support(item, item_root)
        has_stored_road_plate = isinstance(item.get("road_plate"), dict) and bool(item.get("road_plate"))
        if item_root and not (item_policy.get("readOnly") and has_stored_road_plate):
            try:
                _ensure_road_plate_metadata_for_entry(item, item_root)
            except Exception:
                pass
        road_plate = dict(item.get("road_plate") or {}) if isinstance(item.get("road_plate"), dict) else {}
        road_plate_project_png = _road_plate_project_png_path_for_entry(item)
        road_plate_local_png = _road_plate_local_skin_png_path_for_entry(item)
        road_plate_effective_png = _road_plate_effective_png_path_for_entry(item)
        road_plate_location = _road_plate_png_location_for_entry(item, road_plate_effective_png)
        road_plate["projectPngPath"] = str(road_plate_effective_png)
        road_plate["projectPngExists"] = bool(road_plate_effective_png.exists())
        road_plate["projectPngHash"] = _file_sha256(road_plate_effective_png)
        road_plate["projectPngLocation"] = road_plate_location
        road_plate["projectPngEditable"] = road_plate_location == "project"
        road_plate["liveryProjectPngPath"] = str(road_plate_project_png)
        road_plate["localSkinPngPath"] = str(road_plate_local_png) if road_plate_local_png else ""
        road_plate["generatedAssets"] = _road_plate_generated_assets_state_for_entry(item)
        tyre_brand = describe_my_skin_tyre_brand(item, item_root) if item_root else {"selection": "default", "brand": "", "label": "Default", "source": "missing", "refs": []}
        tyre_brand = _presented_tyre_brand_selection(
            tyre_brand,
            item.get("tyre_brand") if isinstance(item.get("tyre_brand"), dict) else {},
        )
        if item_root:
            try:
                repair_missing_skin_local_material_dependencies(item, item_root)
            except Exception:
                pass
        project_asset_status = inspect_my_skin_project_assets(item, item_policy)
        activation_dependencies = inspect_my_skin_activation_dependencies(item, item_root) if item_root else {"critical": [], "criticalCount": 0, "warnings": [], "warningCount": 0}
        game_sync = item.get("game_sync") if isinstance(item.get("game_sync"), dict) else {}
        driver_project_sync = _driver_project_game_sync_state_for_entry(item)
        if driver_project_sync:
            game_sync = _merge_my_skin_game_sync_states(game_sync, driver_project_sync) if game_sync.get("needs_update") else driver_project_sync
        road_plate_sync = _road_plate_game_sync_state_for_entry(item)
        if road_plate_sync:
            game_sync = _merge_my_skin_game_sync_states(game_sync, road_plate_sync) if game_sync.get("needs_update") else road_plate_sync
        policy = item_policy
        rows.append(
            {
                **item,
                "origin": policy.get("origin") or skin_policy.skin_origin(item),
                "project_type": item_project_type,
                "projectType": item_project_type,
                "project_state": policy.get("projectState") or skin_policy.project_state(item),
                "policy": _json_ready(policy),
                "capabilities": _json_ready(item_capabilities),
                "capabilityDetails": _json_ready(item_capability_details),
                "capabilityRows": _json_ready(project_capabilities.capability_rows(item_capabilities)),
                "official_external_inspection": _json_ready(official_external_inspection),
                "official_external_validation": _json_ready(official_external_validation),
                "officialExternalValidation": _json_ready(official_external_validation),
                "deployment_preflight": _json_ready(official_external_preflight),
                "deploymentPreflight": _json_ready(official_external_preflight),
                "game_sync": _json_ready(game_sync) if game_sync else {},
                "source_skin": _json_ready(source_skin) if source_skin else _json_ready(item.get("source_skin") or {}),
                "files_list": file_rows,
                "file_count": len(file_rows),
                "present_count": present,
                "missing_count": missing,
                "status": "complete" if file_rows and missing == 0 else ("partial" if present else "missing"),
                "hidden": hidden,
                "active": effective_active,
                "stored_active": active,
                "official_deployment_health": _json_ready(official_deployment_health),
                "local_root": str(local_root) if local_root else local_root_raw,
                "local_root_exists": bool(local_root),
                "game_root": str(item_root) if item_root else str(item.get("game_root") or ""),
                "thumbnail_png_path": (game_rel_from_disk(local_root, thumbnail_png_disk) if local_root else None) or (game_rel_from_disk(item_root, thumbnail_png_disk) if item_root else "") or "",
                "thumbnail_png_disk_path": str(thumbnail_png_disk) if thumbnail_png_disk and thumbnail_png_disk.exists() else "",
                "project_thumbnail_disk_path": str(project_thumbnail_disk) if project_thumbnail_disk and project_thumbnail_disk.exists() else "",
                "thumbnail_mtime_ns": _file_signature(project_thumbnail_disk or thumbnail_png_disk).get("mtime_ns") if (project_thumbnail_disk or thumbnail_png_disk) else 0,
                "thumbnail_size": _file_signature(project_thumbnail_disk or thumbnail_png_disk).get("size") if (project_thumbnail_disk or thumbnail_png_disk) else 0,
                "preview_source": _my_skin_preview_source(item),
                "effective_thumbnail_disk_path": effective_thumbnail_disk_path,
                "ace_snapshot": _json_ready(ace_snapshot),
                "numberplate_variant": _json_ready(numberplate_variant),
                "numberplate_support": _json_ready(numberplate_support),
                "road_plate": _json_ready(road_plate),
                "numberplate_bake": _json_ready(item.get("numberplate_bake") or {}),
                "tyre_brand": _json_ready(tyre_brand),
                "project_asset_status": _json_ready(project_asset_status),
                "activation_dependencies": _json_ready(activation_dependencies),
            }
        )
    rows.sort(key=lambda row: (str(row.get("car_model", "")).lower(), str(row.get("display_name", "")).lower(), str(row.get("created_at", "")).lower()))
    return rows


def _skin_package_relative_path(package_root: Path, path: Path) -> str:
    return normalize_game_rel_path(str(path.resolve().relative_to(package_root.resolve())))


def _skin_package_custom_tyre_brand_manifests(entry: dict[str, Any], game_root: Path) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    visual_rel = str(files.get("visual_preset") or "").strip()
    visual_path = _skin_file_disk_path(game_root, entry, visual_rel)
    manifests: dict[str, dict[str, Any]] = {}
    missing: list[dict[str, Any]] = []
    if visual_path and visual_path.exists():
        for row in _visualpreset_tyretexture_refs(visual_path.read_bytes()):
            ref = normalize_game_rel_path(str(row.get("path") or ""))
            if not ref:
                continue
            manifest = _custom_tyre_manifest_for_ref(ref)
            if manifest:
                manifests[str(manifest.get("id") or manifest.get("_manifestPath"))] = manifest
                continue
            ref_lower = ref.lower()
            looks_custom = (
                "\\common_assets\\parts\\tyres\\textures\\racing_" in ref_lower
                and "racing_generic" not in ref_lower
                and not resolve_content_path(game_root, ref).exists()
            )
            if looks_custom:
                missing.append({"kind": "tyreBrand", "path": ref, "reason": "custom tyre brand dependency not found"})
    tyre_brand = entry.get("tyre_brand") if isinstance(entry.get("tyre_brand"), dict) else {}
    selection = str(tyre_brand.get("selection") or tyre_brand.get("brand") or "").strip()
    if selection and selection.lower() != "default":
        manifest = _custom_tyre_brand_manifest_by_id(selection)
        if manifest:
            manifests[str(manifest.get("id") or manifest.get("_manifestPath"))] = manifest
    return list(manifests.values()), missing


def _skin_numberplate_template_available(template_id: str | None) -> bool:
    return bool(template_id and _numberplate_studio_template(template_id))


SKIN_PACKAGE_PORTABLE_METADATA_DROP_KEYS = {
    "manifestPath",
    "assetRoot",
    "profileAssetRoot",
    "profileManifestPath",
    "projectPath",
    "projectRoot",
    "projectAssetRoot",
    "sourcePngPath",
    "sourcePath",
    "texturePath",
    "texturemipsPath",
    "pngPath",
    "previewPngPath",
    "sourcePngOriginalPath",
    "sourceBlurPngOriginalPath",
    "markPngOriginalPath",
    "disk_path",
    "diskPath",
    "thumbnail_png_disk_path",
    "effective_thumbnail_disk_path",
    "projectPngPath",
    "projectPngHash",
    "projectPngLocation",
    "projectPngEditable",
    "liveryProjectPngPath",
    "localSkinPngPath",
    "generatedAssets",
    "lastSyncedProjectPngHash",
}


def _skin_package_portable_metadata(value: Any) -> Any:
    if isinstance(value, dict):
        return {
            str(key): _skin_package_portable_metadata(val)
            for key, val in value.items()
            if str(key) not in SKIN_PACKAGE_PORTABLE_METADATA_DROP_KEYS
        }
    if isinstance(value, list):
        return [_skin_package_portable_metadata(item) for item in value]
    return _json_ready(value)


def _skin_package_road_plate_metadata(value: Any) -> dict[str, Any]:
    if not isinstance(value, dict) or not value:
        return {}
    plate = _skin_package_portable_metadata(value)
    if not isinstance(plate, dict):
        return {}
    source = str(plate.get("source") or "").strip().lower()
    if source not in {"custom", "default"}:
        source = "custom" if any(plate.get(key) for key in ("textureRel", "materialRel", "meshRel", "carPartRel", "compatiblePartRel")) else "default"
    result = {
        "supported": bool(plate.get("supported", True)),
        "kind": str(plate.get("kind") or "road_rear"),
        "label": str(plate.get("label") or road_plates.PLATE_LABEL),
        "source": source,
        "projectPng": str(plate.get("projectPng") or (road_plates.PROJECT_PLATE_PNG if source == "custom" else "")).strip(),
    }
    for key in (
        "sourceTextureRel",
        "sourceMaterialRel",
        "sourceMeshRel",
        "plateFamily",
        "plateSide",
        "textureWidth",
        "textureHeight",
        "textureMips",
        "textureRel",
        "mipsRel",
        "materialRel",
        "meshRel",
        "carPartRel",
        "compatiblePartRel",
    ):
        if plate.get(key) not in (None, ""):
            result[key] = plate.get(key)
    return _json_ready(result)


def _skin_package_driver_profile_dependency(selection: dict[str, Any], car_model: str) -> dict[str, Any] | None:
    if not _driver_profile_selection({"driver_profile": selection}):
        return None
    profile_id = str(selection.get("profileId") or selection.get("id") or "").strip()
    if not profile_id:
        return None
    profile = _driver_profile_from_selection(selection, car_model)
    return {
        "kind": "driverProfile",
        "id": profile_id,
        "carModel": str(selection.get("carModel") or car_model or "").strip(),
        "label": str((profile or {}).get("name") or selection.get("label") or selection.get("name") or profile_id),
        "required": True,
        "embedded": False,
        "available": bool(profile),
    }


def _driver_profile_package_root(package_root: Path, car_model: str, profile_id: str) -> Path:
    car_slug = driver_library.sanitize_slug(car_model, "car")
    profile_slug = driver_library.sanitize_slug(profile_id, "driver")
    return package_root / "dependencies" / "driver_profiles" / car_slug / profile_slug


def _driver_profile_package_target(car_model: str, profile_id: str) -> Path:
    return get_driver_profiles_root() / driver_library.sanitize_slug(car_model, "car") / driver_library.sanitize_slug(profile_id, "driver")


def _driver_project_package_target(car_model: str, profile_id: str) -> Path:
    return driver_projects.project_path_for(get_livery_projects_root(), car_model, profile_id).parent


def _driver_profile_unique_id(car_model: str, preferred_id: str) -> str:
    base = driver_library.sanitize_slug(preferred_id, "driver")
    for index in range(1, 1000):
        candidate = base if index == 1 else f"{base}_{index}"
        profile_target = _driver_profile_package_target(car_model, candidate)
        project_target = _driver_project_package_target(car_model, candidate)
        if (
            not driver_library.get_driver_profile(get_livery_library_root(), candidate, car_model or None)
            and not profile_target.exists()
            and not project_target.exists()
        ):
            return candidate
    return f"{base}_{datetime.now().strftime('%H%M%S')}"


def _copy_tree_fresh(source: Path, target: Path) -> int:
    if target.exists():
        shutil.rmtree(target)
    shutil.copytree(source, target)
    return sum(1 for child in target.rglob("*") if child.is_file())


def _rewrite_driver_profile_manifest_for_package(profile_dir: Path) -> None:
    manifest_path = profile_dir / driver_library.DRIVER_MANIFEST_FILENAME
    if not manifest_path.exists():
        return
    try:
        manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    except Exception:
        return
    if not isinstance(manifest, dict):
        return
    manifest = _skin_package_portable_metadata(manifest)
    manifest_path.write_text(json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False), encoding="utf-8")


def _rewrite_driver_project_manifest_for_package(project_dir: Path) -> None:
    project_path = project_dir / driver_projects.DRIVER_PROJECT_FILENAME
    if not project_path.exists():
        return
    try:
        project = json.loads(project_path.read_text(encoding="utf-8"))
    except Exception:
        return
    if not isinstance(project, dict):
        return
    project = _skin_package_portable_metadata(project)
    project_path.write_text(json.dumps(_json_ready(project), indent=2, ensure_ascii=False), encoding="utf-8")


def _copy_label_for_renamed_import(value: Any, fallback: str) -> str:
    text = str(value or "").strip() or fallback
    return text if text.lower().endswith(" copy") else f"{text} Copy"


def _rewrite_imported_driver_profile_manifest(
    profile_dir: Path,
    project_dir: Path | None = None,
    imported_id: str | None = None,
    renamed: bool = False,
) -> None:
    manifest_path = profile_dir / driver_library.DRIVER_MANIFEST_FILENAME
    if not manifest_path.exists():
        return
    try:
        manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    except Exception:
        return
    if not isinstance(manifest, dict):
        return
    for key in SKIN_PACKAGE_PORTABLE_METADATA_DROP_KEYS:
        manifest.pop(key, None)
    if imported_id:
        manifest["id"] = driver_library.sanitize_slug(imported_id, "driver")
    if renamed:
        manifest["name"] = _copy_label_for_renamed_import(manifest.get("name"), manifest.get("id") or "Driver")
    if project_dir is not None:
        manifest["projectRoot"] = str(project_dir)
        manifest["projectPath"] = str(project_dir / driver_projects.DRIVER_PROJECT_FILENAME)
        manifest["projectAssetRoot"] = str(profile_dir / "assets")
    manifest["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    manifest_path.write_text(json.dumps(_json_ready(manifest), indent=2, ensure_ascii=False), encoding="utf-8")


def _rewrite_imported_driver_project_manifest(
    project_dir: Path,
    profile_dir: Path,
    imported_id: str | None = None,
    renamed: bool = False,
) -> None:
    project_path = project_dir / driver_projects.DRIVER_PROJECT_FILENAME
    if not project_path.exists():
        return
    driver_projects.ensure_project_readme(project_dir)
    try:
        project = json.loads(project_path.read_text(encoding="utf-8"))
    except Exception:
        return
    if not isinstance(project, dict):
        return
    if imported_id:
        project["profileId"] = driver_library.sanitize_slug(imported_id, "driver")
        project["id"] = driver_library.sanitize_slug(imported_id, "driver")
    if renamed:
        project["name"] = _copy_label_for_renamed_import(project.get("name"), project.get("profileId") or project.get("id") or "Driver")
    project["projectRoot"] = str(project_dir)
    project["projectPath"] = str(project_path)
    project["assetRoot"] = str(project_dir / "assets")
    project["profileManifestPath"] = str(profile_dir / driver_library.DRIVER_MANIFEST_FILENAME)
    project["profileAssetRoot"] = str(profile_dir)
    for source in project.get("textureSources") or []:
        if not isinstance(source, dict):
            continue
        source_png = str(source.get("sourcePng") or "").strip()
        if source_png:
            source["sourcePngPath"] = str(project_dir / source_png)
        uv_png = str(source.get("uvTemplatePng") or "").strip()
        if uv_png:
            source["uvTemplatePngPath"] = str(project_dir / uv_png)
        template = source.get("uvTemplate") if isinstance(source.get("uvTemplate"), dict) else {}
        template_png = str(template.get("uvTemplatePng") or "").strip()
        if template_png:
            template["uvTemplatePngPath"] = str(project_dir / template_png)
    for template in project.get("uvTemplates") or []:
        if not isinstance(template, dict):
            continue
        template_png = str(template.get("uvTemplatePng") or template.get("name") or "").strip()
        if template_png:
            template["uvTemplatePngPath"] = str(project_dir / template_png)
    project["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    project_path.write_text(json.dumps(_json_ready(project), indent=2, ensure_ascii=False), encoding="utf-8")


def _export_skin_package_driver_profile(package_root: Path, selection: dict[str, Any], car_model: str) -> dict[str, Any] | None:
    profile = _driver_profile_from_selection(selection, car_model)
    if not profile:
        return None
    profile_id = str(profile.get("id") or selection.get("profileId") or selection.get("id") or "").strip()
    profile_car = str(profile.get("carModel") or selection.get("carModel") or car_model or "").strip()
    if not profile_id or not profile_car:
        return None
    source_profile_root = Path(str(profile.get("assetRoot") or "")).expanduser()
    if not source_profile_root.exists():
        return None
    package_driver_root = _driver_profile_package_root(package_root, profile_car, profile_id)
    profile_package_root = package_driver_root / "profile"
    profile_file_count = _copy_tree_fresh(source_profile_root, profile_package_root)
    _rewrite_driver_profile_manifest_for_package(profile_package_root)

    project_file_count = 0
    project_embedded = False
    project_path = driver_projects.project_path_for(get_livery_projects_root(), profile_car, profile_id)
    project_root = project_path.parent
    if project_path.exists() and project_root.exists():
        project_package_root = package_driver_root / "project"
        project_file_count = _copy_tree_fresh(project_root, project_package_root)
        _rewrite_driver_project_manifest_for_package(project_package_root)
        project_embedded = True

    return {
        "kind": "driverProfile",
        "id": profile_id,
        "carModel": profile_car,
        "label": str(profile.get("name") or selection.get("label") or selection.get("name") or profile_id),
        "required": True,
        "embedded": True,
        "projectEmbedded": project_embedded,
        "packagePath": _skin_package_relative_path(package_root, package_driver_root),
        "fileCount": profile_file_count,
        "projectFileCount": project_file_count,
    }


def export_driver_profile_package(profile_id: str, car_model: str | None = None, out_dir: str | Path | None = None, root: str | Path | None = None) -> dict[str, Any]:
    clean_profile_id = str(profile_id or "").strip()
    if not clean_profile_id:
        raise ValueError("Driver profile id is required")
    profile = driver_library.get_driver_profile(get_livery_library_root(), clean_profile_id, car_model or None)
    if not profile:
        raise FileNotFoundError(f"Driver profile not found: {clean_profile_id}")

    profile_id = str(profile.get("id") or clean_profile_id).strip()
    profile_car = str(profile.get("carModel") or car_model or "").strip()
    if not profile_id or not profile_car:
        raise ValueError("Driver profile id and car model are required")
    driver_library.require_driver_car_supported(profile_car)

    source_profile_root = Path(str(profile.get("assetRoot") or "")).expanduser()
    if not (source_profile_root / driver_library.DRIVER_MANIFEST_FILENAME).exists():
        raise FileNotFoundError(f"Driver profile manifest not found: {source_profile_root}")

    export_root = _driver_profile_package_export_root(out_dir, root)
    package_name = short_export_package_name(
        "driver",
        profile.get("name") or profile_id,
        profile_car,
        profile_id,
    )
    package_root = export_root / package_name
    if package_root.exists():
        shutil.rmtree(package_root)
    package_root.mkdir(parents=True, exist_ok=True)

    profile_package_root = package_root / "profile"
    profile_file_count = _copy_tree_fresh(source_profile_root, profile_package_root)
    _rewrite_driver_profile_manifest_for_package(profile_package_root)

    project_file_count = 0
    project_embedded = False
    project_path = driver_projects.project_path_for(get_livery_projects_root(), profile_car, profile_id)
    project_root = project_path.parent
    if project_path.exists() and project_root.exists():
        project_package_root = package_root / "project"
        project_file_count = _copy_tree_fresh(project_root, project_package_root)
        _rewrite_driver_project_manifest_for_package(project_package_root)
        project_embedded = True

    package_manifest = {
        "formatVersion": 1,
        "type": DRIVER_PROFILE_PACKAGE_TYPE,
        "app": {"name": APP_NAME, "version": APP_VERSION},
        "exportedAt": datetime.now().isoformat(timespec="seconds"),
        "driver": {
            "id": profile_id,
            "carModel": profile_car,
            "label": str(profile.get("name") or profile_id),
            "profilePath": "profile\\manifest.json",
            "projectPath": "project\\driver_project.json" if project_embedded else "",
        },
        "profileFileCount": profile_file_count,
        "projectEmbedded": project_embedded,
        "projectFileCount": project_file_count,
    }
    manifest_path = package_root / "manifest.json"
    manifest_path.write_text(json.dumps(_json_ready(package_manifest), indent=2, ensure_ascii=False), encoding="utf-8")
    zip_path = package_zip.export_zip_path(export_root, package_name)
    package_zip.zip_directory(package_root, zip_path)
    shutil.rmtree(package_root, ignore_errors=True)
    return _json_ready({
        "packageRoot": zip_path,
        "packagePath": zip_path,
        "zip": True,
        "manifestPath": "manifest.json",
        "driver": package_manifest["driver"],
        "profileFileCount": profile_file_count,
        "projectEmbedded": project_embedded,
        "projectFileCount": project_file_count,
    })


def _inspect_driver_profile_package_root(package_root: str | Path, package_path: str | Path | None = None) -> dict[str, Any]:
    package_root = Path(package_root).expanduser().resolve()
    validate_package_import_structure(
        package_root,
        {"manifest.json", "profile", "project"},
        "Driver profile package",
    )
    manifest_path = package_root / "manifest.json"
    if not manifest_path.exists():
        raise FileNotFoundError(f"manifest.json not found in package folder: {package_root}")
    manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    if not isinstance(manifest, dict) or manifest.get("type") != DRIVER_PROFILE_PACKAGE_TYPE:
        raise ValueError("Driver profile package manifest is invalid")
    driver_info = manifest.get("driver") if isinstance(manifest.get("driver"), dict) else {}
    profile_id = str(driver_info.get("id") or "").strip()
    car_model = str(driver_info.get("carModel") or "").strip()
    driver_support = driver_library.driver_support_info(car_model=car_model)
    profile_manifest = _skin_package_member_path(package_root, str(driver_info.get("profilePath") or "profile\\manifest.json"), "driver profile package manifest")
    project_rel = str(driver_info.get("projectPath") or "").strip()
    project_manifest = _skin_package_member_path(package_root, project_rel, "driver profile package project") if project_rel else package_root / "project" / driver_projects.DRIVER_PROJECT_FILENAME

    missing: list[dict[str, Any]] = []
    profile_manifest_data: dict[str, Any] = {}
    if not profile_manifest.exists():
        missing.append({"kind": "driverProfile", "path": normalize_game_rel_path(str(profile_manifest.relative_to(package_root))), "reason": "profile manifest missing"})
    else:
        try:
            profile_manifest_data = json.loads(profile_manifest.read_text(encoding="utf-8"))
        except Exception:
            profile_manifest_data = {}
        if not isinstance(profile_manifest_data, dict) or profile_manifest_data.get("type") != driver_library.DRIVER_PROFILE_TYPE:
            missing.append({"kind": "driverProfile", "path": normalize_game_rel_path(str(profile_manifest.relative_to(package_root))), "reason": "profile manifest invalid"})

    profile_root = profile_manifest.parent
    if isinstance(profile_manifest_data, dict):
        for rel in profile_manifest_data.get("files") or []:
            rel_text = normalize_game_rel_path(str(rel or ""))
            if not rel_text:
                continue
            file_path = _skin_package_member_path(profile_root, rel_text, "driver profile file")
            if not file_path.exists():
                asset_file_path = _skin_package_member_path(profile_root / "assets", rel_text, "driver profile asset file")
                if asset_file_path.exists():
                    file_path = asset_file_path
            if not file_path.exists():
                missing.append({"kind": "driverProfileFile", "path": rel_text, "reason": "file missing"})
    profile_file_count = sum(1 for child in profile_root.rglob("*") if child.is_file()) if profile_root.exists() else 0
    project_embedded = project_manifest.exists()
    project_file_count = sum(1 for child in project_manifest.parent.rglob("*") if child.is_file()) if project_embedded else 0
    installed = bool(profile_id and driver_library.get_driver_profile(get_livery_library_root(), profile_id, car_model or None))

    return _json_ready({
        "packageRoot": package_root,
        "manifestPath": manifest_path,
        "manifest": manifest,
        "driver": {**driver_info, "manifest": profile_manifest_data},
        "driverSupport": driver_support,
        "profileEmbedded": profile_manifest.exists(),
        "profileFileCount": profile_file_count,
        "projectEmbedded": project_embedded,
        "projectFileCount": project_file_count,
        "installed": installed,
        "missing": missing,
        "missingCount": len(missing),
        "packagePath": str(package_path or package_root),
        "zip": package_zip.is_zip_package(package_path or ""),
    })


def inspect_driver_profile_package(package_dir: str | Path) -> dict[str, Any]:
    source = Path(package_dir).expanduser()
    with package_zip.package_root(source, get_package_import_temp_root(), allow_folder=True) as package_root:
        return _inspect_driver_profile_package_root(package_root, source)


def import_driver_profile_package(package_dir: str | Path, conflict_mode: str = "rename") -> dict[str, Any]:
    source_package = Path(package_dir).expanduser()
    with package_zip.package_root(source_package, get_package_import_temp_root(), allow_folder=True) as package_root:
        return _import_driver_profile_package_root(package_root, source_package, conflict_mode)


def _import_driver_profile_package_root(package_root: Path, source_package: Path, conflict_mode: str = "rename") -> dict[str, Any]:
    info = _inspect_driver_profile_package_root(package_root, source_package)
    if info.get("missingCount"):
        raise FileNotFoundError("Driver profile package is incomplete")
    package_root = Path(str(info["packageRoot"]))
    driver_info = info.get("driver") if isinstance(info.get("driver"), dict) else {}
    profile_id = str(driver_info.get("id") or "").strip()
    car_model = str(driver_info.get("carModel") or "").strip()
    if not profile_id or not car_model:
        raise ValueError("Driver profile id and car model are required")
    driver_library.require_driver_car_supported(car_model)

    existing = driver_library.get_driver_profile(get_livery_library_root(), profile_id, car_model or None)
    mode = str(conflict_mode or "rename").strip().lower()
    replace_existing = mode in {"replace", "overwrite"}
    if existing and not replace_existing:
        imported_profile_id = _driver_profile_unique_id(car_model, profile_id)
    else:
        imported_profile_id = profile_id
    renamed = imported_profile_id != profile_id
    if existing and replace_existing:
        return _json_ready({
            "status": "already_installed",
            "imported": False,
            "driver": _attach_driver_profile_usage(driver_projects.attach_project_info(get_livery_projects_root(), existing)),
            "profileId": profile_id,
            "carModel": car_model,
            "projectImported": False,
            "conflictMode": "replace",
        })

    profile_manifest = _skin_package_member_path(package_root, str(driver_info.get("profilePath") or "profile\\manifest.json"), "driver profile package manifest")
    profile_source = profile_manifest.parent
    target_profile_root = _driver_profile_package_target(car_model, imported_profile_id)
    if target_profile_root.exists():
        raise FileExistsError(f"Target driver profile folder already exists: {target_profile_root}")
    copy_package_import_tree(profile_source, target_profile_root, "driver profile package target")

    project_import: dict[str, Any] = {"status": "missing", "reason": "embedded driver project missing"}
    target_project_root: Path | None = None
    project_rel = str(driver_info.get("projectPath") or "").strip()
    project_manifest = _skin_package_member_path(package_root, project_rel, "driver profile package project") if project_rel else package_root / "project" / driver_projects.DRIVER_PROJECT_FILENAME
    project_source = project_manifest.parent
    if project_manifest.exists():
        target_project_root = _driver_project_package_target(car_model, imported_profile_id)
        if target_project_root.exists():
            project_import = {"status": "already_installed", "target": str(target_project_root)}
        else:
            copy_package_import_tree(project_source, target_project_root, "driver project package target")
            _rewrite_imported_driver_project_manifest(target_project_root, target_profile_root, imported_profile_id, renamed)
            project_import = {
                "status": "imported",
                "target": str(target_project_root),
                "fileCount": sum(1 for child in target_project_root.rglob("*") if child.is_file()),
            }

    _rewrite_imported_driver_profile_manifest(target_profile_root, target_project_root, imported_profile_id, renamed)
    imported_profile = driver_library.get_driver_profile(get_livery_library_root(), imported_profile_id, car_model or None) or {}
    if imported_profile:
        imported_profile = _attach_driver_profile_usage(driver_projects.attach_project_info(get_livery_projects_root(), imported_profile))
    return _json_ready({
        "status": "imported",
        "imported": True,
        "packageRoot": package_root,
        "packagePath": source_package,
        "target": target_profile_root,
        "profileId": imported_profile_id,
        "sourceProfileId": profile_id,
        "carModel": car_model,
        "driver": imported_profile,
        "fileCount": sum(1 for child in target_profile_root.rglob("*") if child.is_file()),
        "project": project_import,
        "projectImported": project_import.get("status") == "imported",
        "conflictMode": "rename" if renamed else "none",
        "renamed": renamed,
    })


def _official_package_embedded_saved_car_metadata(
    details: Any,
    saved_car: dict[str, Any],
) -> dict[str, Any]:
    portable = official_package_portability.sanitize_official_metadata(details)
    filename = Path(str(saved_car.get("filename") or saved_car.get("path") or "")).name
    if not filename:
        return portable
    relative_path = norm_slashes(str(Path("SavedCar") / filename))
    previous = portable.get("generatedSavedCar") if isinstance(portable.get("generatedSavedCar"), dict) else {}
    generated = official_package_portability.sanitize_official_metadata(previous)
    generated.update({
        "sourceType": official_saved_car_generation.SOURCE_PACKAGE_EMBEDDED,
        "relativePath": relative_path,
        "filename": filename,
        "carGuid": str(saved_car.get("carGuid") or generated.get("carGuid") or ""),
        "carModel": str(saved_car.get("carModel") or generated.get("carModel") or ""),
        "sha256": str(saved_car.get("sha256") or generated.get("sha256") or ""),
        "size": int(saved_car.get("size") or generated.get("size") or 0),
    })
    generated.pop("path", None)
    generated.pop("deployedPath", None)
    portable["savedCarSource"] = official_saved_car_generation.SOURCE_PACKAGE_EMBEDDED
    portable["generatedSavedCar"] = generated
    if generated["carGuid"]:
        portable["carGuid"] = generated["carGuid"]
        portable["linkedSavedCarGuid"] = generated["carGuid"]
    return portable


def export_my_skin_package(
    root: Path,
    skin_key: str,
    out_dir: Path,
    package_mode: str = "read_only",
    *,
    _legacy_migration_staging: bool = False,
) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    key = str(skin_key or "").strip().lower()
    mode = str(package_mode or "read_only").strip().lower()
    if mode not in {"read_only", "editable_project"}:
        mode = "read_only"
    if not key:
        raise ValueError("Skin key is required")
    if not out_dir:
        raise ValueError("Export folder is required")

    skin = next((row for row in list_my_skins(game_root) if str(row.get("key", "")).lower() == key), None)
    if not skin:
        raise KeyError(f"My Skin not found: {skin_key}")
    if not (_legacy_migration_staging and mode == "read_only"):
        _assert_my_skin_policy(skin, "canExportPackage")

    car_model = str(skin.get("car_model", "")).strip() or "car"
    skin_id = str(skin.get("skin_id", "")).strip() or "skin"
    project_type = project_types.project_type_for_entry(skin)
    if project_type == project_types.PROJECT_TYPE_ADVANCED_EXTRACTED_CONTENT:
        banner_dependency_repair = repair_advanced_ext_banner_runtime_dependencies(skin, game_root)
        if banner_dependency_repair.get("required") and not banner_dependency_repair.get("ready"):
            raise ValueError(
                "Advanced package export is not ready because its local EXT_BANNER runtime files could not be prepared: "
                f"{banner_dependency_repair.get('reason') or 'unknown banner repair error'}"
            )
    package_name = short_export_package_name(
        "skin_project" if mode == "editable_project" else "skin",
        skin_id if livery_identity.identity_locked(skin) else (skin.get("display_name") or skin_id),
        car_model,
        skin_id,
    )
    package_root = out_dir / package_name
    if package_root.exists():
        shutil.rmtree(package_root)
    package_root.mkdir(parents=True, exist_ok=True)

    if project_type == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
        project_folder = _livery_project_folder_for_entry(skin)
        if not project_folder.exists() or not project_folder.is_dir():
            raise FileNotFoundError(f"Official external project folder not found: {project_folder}")
        package_validation = official_package_validation.validate_payload(
            project_folder,
            skin,
            allow_linked_saved_car=True,
        )
        if not package_validation.get("ready"):
            shutil.rmtree(package_root, ignore_errors=True)
            errors = [str(value) for value in package_validation.get("errors") or [] if str(value).strip()]
            raise ValueError("Official package export is not ready: " + "; ".join(errors[:8]))
        copied: list[dict[str, Any]] = []
        missing: list[dict[str, Any]] = []
        official_details = skin.get("official_external") if isinstance(skin.get("official_external"), dict) else {}
        external_folder_name = official_external_liveries._safe_segment(
            official_details.get("externalFolderName")
            or official_details.get("plannedExternalFolder")
            or official_details.get("sourceFolderName")
            or skin_id,
            skin_id,
        )
        package_content_root = Path("content") / "official_external_liveries" / external_folder_name
        for row in package_validation.get("copyPlan") or []:
            if not isinstance(row, dict):
                continue
            source = Path(str(row.get("source") or ""))
            name = str(row.get("name") or source.name).strip()
            if not name:
                continue
            _name_norm, name_disk = _skin_package_rel_disk_path(name, "Official payload file")
            target_rel = package_content_root / name_disk
            target_rel_text = norm_slashes(str(target_rel))
            package_zip.copy_file(source, package_root / target_rel)
            copied.append({
                "kind": row.get("kind") or "official_external_file",
                "path": target_rel_text,
                "externalLiveryPath": name,
                "type": "file",
                "size": source.stat().st_size,
                "slot": row.get("slot") or "",
                "label": row.get("label") or name,
            })
        saved_car = package_validation.get("savedCar") if isinstance(package_validation.get("savedCar"), dict) else {}
        saved_car_source = Path(str(saved_car.get("path") or "")).expanduser()
        saved_car_rel = package_content_root / "SavedCar" / saved_car_source.name
        package_zip.copy_file(saved_car_source, package_root / saved_car_rel)
        copied.append({
            "kind": "saved_car",
            "path": norm_slashes(str(saved_car_rel)),
            "externalLiveryPath": norm_slashes(str(Path("SavedCar") / saved_car_source.name)),
            "type": "file",
            "size": saved_car_source.stat().st_size,
            "sha256": saved_car.get("sha256") or "",
            "label": "Embedded SavedCar",
        })
        project_manifest: dict[str, Any] = {"included": False}
        if mode == "editable_project":
            project_target = package_root / "project"
            if project_target.exists():
                shutil.rmtree(project_target)
            file_count = 0
            for child in project_folder.rglob("*"):
                if not child.is_file():
                    continue
                package_zip.copy_file(child, project_target / child.relative_to(project_folder))
                file_count += 1
            project_saved_car = project_target / "SavedCar" / saved_car_source.name
            if not project_saved_car.is_file():
                package_zip.copy_file(saved_car_source, project_saved_car)
                file_count += 1
            official_package_portability.sanitize_exported_project_json_tree(project_target, project_folder)
            project_manifest = {
                "included": True,
                "path": "project",
                "sourceFolder": official_package_portability.PROJECT_ROOT_TOKEN,
                "fileCount": file_count,
                "layerStudioProjectLibrary": bool((project_folder / layer_studio.PROJECT_DIRNAME / layer_studio.PROJECT_LIBRARY_DIRNAME / layer_studio.LIBRARY_FILE_NAME).exists()),
            }
        portable_official_details = _official_package_embedded_saved_car_metadata(
            skin.get("official_external") or {},
            saved_car,
        )
        skin_manifest = {
            "key": skin.get("key", ""),
            "skinId": skin.get("skin_id", ""),
            "displayName": skin.get("display_name", ""),
            "carModel": skin.get("car_model", ""),
            "carContentPath": skin.get("car_content_path", ""),
            "projectType": project_type,
            "createdAt": skin.get("created_at", ""),
            "officialExternal": _json_ready(portable_official_details),
            "deploymentTarget": skin.get("deployment_target") or official_external_workflow.DEPLOYMENT_TARGET_SAVED_GAMES_EXTERNAL_LIVERIES,
            "externalFolderName": external_folder_name,
            "carGuid": saved_car.get("carGuid") or skin.get("car_guid") or portable_official_details.get("carGuid") or "",
        }
        manifest = {
            "formatVersion": 3 if mode == "editable_project" else 2,
            "packageMode": mode,
            "projectType": project_type,
            "app": {"name": APP_NAME, "version": APP_VERSION},
            "exportedAt": datetime.now().isoformat(timespec="seconds"),
            "skin": skin_manifest,
            "project": _json_ready(project_manifest),
            "dependencies": {"tyreBrands": [], "numberplateTemplates": [], "driverProfiles": []},
            "officialExternal": {
                "contentRoot": norm_slashes(str(package_content_root)),
            },
            "copied": copied,
            "missing": missing,
        }
        manifest_path = package_root / "manifest.json"
        manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8")
        machine_paths = official_package_portability.machine_paths_in_json_tree(package_root)
        if machine_paths:
            first = machine_paths[0]
            raise ValueError(
                "Official package export still contains a machine-specific path after sanitization: "
                f"{first.get('file') or 'JSON'} at {first.get('jsonPath') or '<root>'}"
            )
        package_path = package_root
        is_zip = False
        if not _legacy_migration_staging:
            package_path = package_zip.export_zip_path(out_dir, package_name)
            package_zip.zip_directory(package_root, package_path)
            shutil.rmtree(package_root, ignore_errors=True)
            is_zip = True
        return {
            "packageRoot": package_path,
            "packagePath": package_path,
            "zip": is_zip,
            "manifestPath": "manifest.json",
            "copiedCount": len(copied),
            "missingCount": len(missing),
            "copied": copied,
            "missing": missing,
            "dependencies": {"tyreBrands": [], "numberplateTemplates": [], "driverProfiles": []},
            "dependencyStatus": {"tyreBrands": [], "numberplateTemplates": [], "driverProfiles": [], "missing": [], "missingCount": 0},
            "packageMode": mode,
            "project": _json_ready(project_manifest),
            "skin": skin,
            "officialExternal": _json_ready(manifest["officialExternal"]),
        }

    copied: list[dict[str, Any]] = []
    missing: list[dict[str, Any]] = []
    dependencies: dict[str, Any] = {"tyreBrands": [], "numberplateTemplates": [], "driverProfiles": []}
    seen_sources: set[str] = set()
    skin_files = _strip_generated_thumbnail_file_refs(dict(skin.get("files", {}) or {}))
    car_content_path = str(skin.get("car_content_path", "")).strip() or f"content\\cars\\{car_model}"
    local_root = _entry_local_root(skin)
    actor_path = resolve_content_path(game_root, str(skin_files.get("actor") or "").strip()) if skin_files.get("actor") else None
    visual_path = resolve_content_path(game_root, str(skin_files.get("visual_preset") or "").strip()) if skin_files.get("visual_preset") else None
    mechanical_path = resolve_content_path(game_root, str(skin_files.get("mechanical_preset") or "").strip()) if skin_files.get("mechanical_preset") else None
    if not actor_path:
        actor_path = find_actor_file(game_root, car_content_path)
        if actor_path and actor_path.exists():
            skin_files["actor"] = game_rel_from_disk(game_root, actor_path)
    if not mechanical_path and actor_path and actor_path.exists():
        try:
            for rel in actor_list_mechanicalpresets(actor_path.read_bytes()):
                candidate = resolve_content_path(game_root, rel)
                if candidate.exists():
                    mechanical_path = candidate
                    skin_files["mechanical_preset"] = game_rel_from_disk(game_root, candidate)
                    break
        except Exception:
            pass
    if visual_path and not mechanical_path:
        mechanical_path = find_mechanicalpreset_for_visual(game_root, actor_path, visual_path, str(skin_files.get("visual_preset") or ""))
        if mechanical_path and mechanical_path.exists():
            skin_files["mechanical_preset"] = game_rel_from_disk(game_root, mechanical_path)
    if visual_path and mechanical_path and not skin_files.get("carfinalstate"):
        carfinalstate_path = find_carfinalstate_for_visual(game_root, str(skin_files.get("visual_preset") or ""), str(skin_files.get("mechanical_preset") or ""))
        if carfinalstate_path and carfinalstate_path.exists():
            skin_files["carfinalstate"] = game_rel_from_disk(game_root, carfinalstate_path)
    if not skin_files.get("carfinalstate") and visual_path and mechanical_path:
        carfinalstate_path = find_carfinalstate_for_visual(game_root, str(skin_files.get("visual_preset") or ""), str(skin_files.get("mechanical_preset") or ""))
        if carfinalstate_path and carfinalstate_path.exists():
            skin_files["carfinalstate"] = game_rel_from_disk(game_root, carfinalstate_path)
    if not skin_files.get("thumbnail_png"):
        thumbnail_png = _resolve_existing_skin_thumbnail_png(game_root, str(skin_files.get("skin_folder") or ""))
        if thumbnail_png and thumbnail_png.exists():
            skin_files["thumbnail_png"] = game_rel_from_disk(game_root, thumbnail_png)
    if visual_path and actor_path and actor_path.exists() and not skin_files.get("mechanical_presets"):
        mechanical_refs = _target_mechanical_preset_refs_for_entry(skin, game_root, actor_path)
        if mechanical_refs:
            skin_files["mechanical_presets"] = mechanical_refs
            skin_files["mechanical_preset"] = skin_files.get("mechanical_preset") or mechanical_refs[0]
    if visual_path and skin_files.get("mechanical_presets") and not skin_files.get("carfinalstates"):
        carfinalstates: list[str] = []
        mechanical_values = skin_files.get("mechanical_presets")
        mechanical_rows = mechanical_values if isinstance(mechanical_values, list) else [mechanical_values]
        for mechanical_ref in mechanical_rows:
            finalstate_path = find_carfinalstate_for_visual(game_root, str(skin_files.get("visual_preset") or ""), str(mechanical_ref or ""))
            if finalstate_path and finalstate_path.exists():
                rel = game_rel_from_disk(game_root, finalstate_path)
                if rel:
                    carfinalstates.append(rel)
        if carfinalstates:
            skin_files["carfinalstates"] = carfinalstates
            skin_files["carfinalstate"] = skin_files.get("carfinalstate") or carfinalstates[0]
    skin_files = _strip_generated_thumbnail_file_refs(skin_files)

    def copy_entry(kind: str, rel_path: str | None) -> None:
        if not rel_path:
            return
        rel_norm, rel_disk = _skin_package_rel_disk_path(rel_path, f"{kind} path")
        source_candidates: list[Path] = []
        if local_root:
            source_candidates.append(local_root / rel_disk)
        source_candidates.append(game_root / rel_disk)
        source = next((candidate for candidate in source_candidates if candidate.exists()), source_candidates[-1])
        source_key = str(source).lower()
        if source_key in seen_sources:
            if source.exists() and source.is_file():
                copied.append({"kind": kind, "path": rel_norm, "type": "file", "size": source.stat().st_size, "alias": True})
            return
        seen_sources.add(source_key)
        if not source.exists():
            missing.append({"kind": kind, "path": rel_norm, "reason": "missing"})
            return
        target = package_root / Path(*split_content_path(rel_norm))
        if source.is_dir():
            if target.exists():
                shutil.rmtree(target)
            file_count = 0
            for child in source.rglob("*"):
                if not child.is_file():
                    continue
                if export_sanitize.is_legacy_numberplate_bake_path(child):
                    continue
                package_zip.copy_file(child, target / child.relative_to(source))
                file_count += 1
            copied.append({"kind": kind, "path": rel_norm, "type": "directory", "files": file_count})
            return
        package_zip.copy_file(source, target)
        copied.append({"kind": kind, "path": rel_norm, "type": "file", "size": source.stat().st_size})

    for kind in ("cardesign", "design", "skin_folder", "material", "actor", "visual_preset", "mechanical_preset", "carfinalstate", "thumbnail_png"):
        copy_entry(kind, skin_files.get(kind))
    for kind in ("mechanical_presets", "carfinalstates"):
        values = skin_files.get(kind)
        rows = values if isinstance(values, list) else [values]
        for row in rows:
            copy_entry(kind, row)
    numberplate_bake = dict(skin.get("numberplate_bake") or {}) if isinstance(skin.get("numberplate_bake"), dict) else {}
    numberplate_variant = (
        skin.get("numberplate_variant")
        if isinstance(skin.get("numberplate_variant"), dict)
        else {}
    )
    road_plate = _skin_package_road_plate_metadata(skin.get("road_plate") or {})
    if NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED:
        for row in skin_package_dependencies.baked_numberplate_paths(numberplate_bake):
            copy_entry(str(row.get("kind") or "numberplate_bake"), row.get("path"))
        numberplate_template_id = str(numberplate_bake.get("template_id") or numberplate_variant.get("template_id") or "").strip()
        numberplate_dependency = skin_package_dependencies.numberplate_template_dependency(
            numberplate_bake,
            numberplate_variant,
            _skin_numberplate_template_available(numberplate_template_id),
        )
        if numberplate_dependency:
            dependencies["numberplateTemplates"].append(numberplate_dependency)
    else:
        numberplate_bake = {}
        numberplate_variant = _sanitize_disabled_numberplate_variant_metadata(numberplate_variant, skin.get("start_number", ""))
    if road_plate.get("source") == "custom":
        generated_rels = _road_plate_generated_rels_for_entry({**skin, "road_plate": road_plate})
        for key, rel in generated_rels.items():
            road_plate.setdefault(key, rel)
        for key in ("textureRel", "mipsRel", "materialRel", "meshRel", "carPartRel", "compatiblePartRel"):
            copy_entry(f"road_plate_{key}", road_plate.get(key))

    driver_profile = (
        _skin_package_portable_metadata(skin.get("driver_profile") or {})
        if isinstance(skin.get("driver_profile"), dict)
        else {}
    )
    driver_dependency = (
        _export_skin_package_driver_profile(package_root, driver_profile, car_model)
        or _skin_package_driver_profile_dependency(driver_profile, car_model)
        if isinstance(driver_profile, dict)
        else None
    )
    if driver_dependency:
        dependencies["driverProfiles"].append(driver_dependency)

    tyre_manifests, missing_tyre_dependencies = _skin_package_custom_tyre_brand_manifests(skin, game_root)
    missing.extend(missing_tyre_dependencies)
    tyre_dependency_root = package_root / "dependencies" / "tyre_brands"
    for tyre_manifest in tyre_manifests:
        brand_id = str(tyre_manifest.get("id") or "").strip()
        if not brand_id:
            continue
        exported = export_custom_tyre_brand_package(brand_id, tyre_dependency_root, game_root, zip_output=False)
        dependency_package = Path(str(exported.get("packageRoot") or ""))
        dependencies["tyreBrands"].append({
            "kind": skin_package_dependencies.DEPENDENCY_KIND_TYRE_BRAND,
            "id": brand_id,
            "label": str(tyre_manifest.get("label") or tyre_manifest.get("brandName") or brand_id),
            "required": True,
            "packagePath": _skin_package_relative_path(package_root, dependency_package),
        })

    skin_manifest = {
        "key": skin.get("key", ""),
        "skinId": skin.get("skin_id", ""),
        "displayName": skin.get("display_name", ""),
        "carModel": skin.get("car_model", ""),
        "carContentPath": skin.get("car_content_path", ""),
        "projectType": project_type,
        "startNumber": skin.get("start_number", ""),
        "visualLocKey": skin.get("visual_loc_key", ""),
        "createdAt": skin.get("created_at", ""),
        "numberplateVariant": _json_ready(numberplate_variant),
        "roadPlate": _json_ready(road_plate),
        "tyreBrand": _skin_package_portable_metadata(skin.get("tyre_brand") or {}),
        "driverProfile": driver_profile,
        "sourceSkin": _skin_package_portable_metadata(skin.get("source_skin") or {}),
    }
    if NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED and numberplate_bake:
        skin_manifest["numberplateBake"] = _json_ready(numberplate_bake)

    project_manifest: dict[str, Any] = {"included": False}
    if mode == "editable_project":
        project_folder = _livery_project_folder_for_entry(skin)
        if not project_folder.exists() or not project_folder.is_dir():
            raise FileNotFoundError(f"LiveryProject folder not found: {project_folder}")
        project_target = package_root / "project"
        if project_target.exists():
            shutil.rmtree(project_target)
        file_count = 0
        for child in project_folder.rglob("*"):
            if not child.is_file():
                continue
            package_zip.copy_file(child, project_target / child.relative_to(project_folder))
            file_count += 1
        project_manifest = {
            "included": True,
            "path": "project",
            "sourceFolder": str(project_folder),
            "fileCount": file_count,
            "layerStudioProjectLibrary": bool((project_folder / layer_studio.PROJECT_DIRNAME / layer_studio.PROJECT_LIBRARY_DIRNAME / layer_studio.LIBRARY_FILE_NAME).exists()),
        }

    manifest = {
        "formatVersion": 3 if mode == "editable_project" else 2,
        "packageMode": mode,
        "projectType": project_type,
        "app": {"name": APP_NAME, "version": APP_VERSION},
        "exportedAt": datetime.now().isoformat(timespec="seconds"),
        "skin": skin_manifest,
        "project": _json_ready(project_manifest),
        "dependencies": _json_ready(dependencies),
        "copied": copied,
        "missing": missing,
    }
    manifest_path = package_root / "manifest.json"
    manifest_path.write_text(json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8")
    dependency_status = _inspect_skin_package_dependency_status(package_root, dependencies, manifest["skin"], copied)
    package_path = package_root
    is_zip = False
    if not _legacy_migration_staging:
        package_path = package_zip.export_zip_path(out_dir, package_name)
        package_zip.zip_directory(package_root, package_path)
        shutil.rmtree(package_root, ignore_errors=True)
        is_zip = True
    return {
        "packageRoot": package_path,
        "packagePath": package_path,
        "zip": is_zip,
        "manifestPath": "manifest.json",
        "copiedCount": len(copied),
        "missingCount": len(missing),
        "copied": copied,
        "missing": missing,
        "dependencies": _json_ready(dependencies),
        "dependencyStatus": _json_ready(dependency_status),
        "packageMode": mode,
        "project": _json_ready(project_manifest),
        "skin": skin,
    }


def _skin_package_rel_disk_path(rel_path: Any, label: str) -> tuple[str, Path]:
    raw_text = str(rel_path or "").strip()
    raw_unix = raw_text.replace("\\", "/")
    if re.match(r"^[A-Za-z]:", raw_unix) or raw_unix.startswith("/"):
        raise ValueError(f"Package {label} is unsafe: {raw_text}")
    rel_norm = norm_slashes(raw_text)
    rel_disk = Path(*split_content_path(rel_norm))
    if not rel_norm or rel_disk.is_absolute() or ".." in rel_disk.parts:
        raise ValueError(f"Package {label} is unsafe: {raw_text}")
    return rel_norm, rel_disk


def _skin_package_member_path(package_root: Path, rel_path: Any, label: str) -> Path:
    rel_norm, rel_disk = _skin_package_rel_disk_path(rel_path, label)
    target = (package_root / rel_disk).resolve()
    try:
        target.relative_to(package_root.resolve())
    except ValueError as exc:
        raise ValueError(f"Package {label} escapes the package: {rel_norm}") from exc
    return target


def _inspect_skin_package_root(package_dir: Path, package_path: str | Path | None = None) -> dict[str, Any]:
    base = Path(package_dir).expanduser()
    validate_package_import_structure(
        base,
        {"manifest.json", "content", "dependencies", "project", "system"},
        "My Skin package",
    )
    package_security.validate_legacy_skin_system_payload(base, label="My Skin package")
    manifest_path = base / "manifest.json"
    if not manifest_path.exists():
        raise FileNotFoundError(f"manifest.json not found in package folder: {base}")
    manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    if not isinstance(manifest, dict):
        raise ValueError("Package manifest is invalid")
    copied = manifest.get("copied", [])
    missing = manifest.get("missing", [])
    skin = manifest.get("skin", {})
    if not isinstance(copied, list):
        copied = []
    if not isinstance(missing, list):
        missing = []
    if not isinstance(skin, dict):
        skin = {}
    dependencies = manifest.get("dependencies") if isinstance(manifest.get("dependencies"), dict) else {}
    dependency_status = _inspect_skin_package_dependency_status(base, dependencies, skin, copied)
    return {
        "packageRoot": base,
        "manifestPath": manifest_path,
        "manifest": manifest,
        "skin": skin,
        "dependencies": dependencies,
        "dependencyStatus": dependency_status,
        "copiedCount": len(copied),
        "missingCount": len(missing),
        "copied": copied,
        "missing": missing,
        "packagePath": str(package_path or base),
        "zip": package_zip.is_zip_package(package_path or ""),
    }


def inspect_skin_package(package_dir: Path) -> dict[str, Any]:
    source = Path(package_dir).expanduser()
    with package_zip.package_root(source, get_package_import_temp_root(), allow_folder=True) as package_root:
        return _inspect_skin_package_root(package_root, source)


def _advanced_package_official_resource_status(plan: dict[str, Any], root: str | Path | None = None) -> dict[str, Any]:
    car_model = str(plan.get("carModel") or "").strip()
    status = get_official_car_resources_status(root)
    cars = status.get("creatorCars") if isinstance(status.get("creatorCars"), list) else []
    car = next((row for row in cars if isinstance(row, dict) and str(row.get("carId") or "").strip().lower() == car_model.lower()), {})
    variants = car.get("vehicleVariants") if isinstance(car.get("vehicleVariants"), list) else []
    inspected_variants: list[dict[str, Any]] = []
    compatible_variants: list[dict[str, Any]] = []
    preview_assets_root = get_official_preview_assets_root(create=False)
    package_skin = plan.get("skin") if isinstance(plan.get("skin"), dict) else {}
    source_skin = package_skin.get("sourceSkin") if isinstance(package_skin.get("sourceSkin"), dict) else {}
    preferred_mechanical = norm_slashes(str(source_skin.get("mechanical_preset") or source_skin.get("mechanicalPreset") or "")).lower()
    for variant in variants:
        if not isinstance(variant, dict):
            continue
        source_status = official_saved_car_generation.resolve_variant_final_state(preview_assets_root, car_model, variant)
        compatibility = official_variant_compatibility.compatibility(
            source_status.get("inspection") if isinstance(source_status.get("inspection"), dict) else {},
            variant,
            preview_assets_root=preview_assets_root,
        )
        row = {
            **variant,
            "officialExternalCompatible": bool(source_status.get("ready") and compatibility.get("compatible")),
            "officialExternalCompatibility": _json_ready(compatibility),
            "savedCarSourceStatus": _json_ready(source_status),
        }
        inspected_variants.append(row)
        if row["officialExternalCompatible"]:
            compatible_variants.append(row)
    default_id = str(car.get("defaultVehicleVariantId") or "").strip()
    default_variant = advanced_to_official_import.select_compatible_official_variant(
        inspected_variants,
        default_id=default_id,
        preferred_mechanical=preferred_mechanical,
    )
    if default_variant is None:
        default_variant = None
    blockers: list[str] = []
    if not status.get("ready"):
        blockers.append("Official Preview Assets are not prepared")
    if not car:
        blockers.append(f"Target car is not available in the Official creator catalog: {car_model or '-'}")
    elif not car.get("ready"):
        blockers.append(f"Official creator assets are incomplete for {car_model}")
    if not default_variant:
        blockers.append(f"No compatible Official vehicle variant is available for {car_model}")
    return {
        "ready": not blockers,
        "blockers": blockers,
        "resourceReady": bool(status.get("ready")),
        "car": _json_ready(car),
        "vehicleVariants": _json_ready(inspected_variants),
        "compatibleVehicleVariants": _json_ready(compatible_variants),
        "defaultVehicleVariant": _json_ready(default_variant) if default_variant else {},
        "defaultVehicleVariantId": str((default_variant or {}).get("id") or ""),
    }


def _advanced_official_duplicate_status(plan: dict[str, Any]) -> dict[str, Any]:
    fingerprint = str(plan.get("contentFingerprint") or "").strip().lower()
    car_model = str(plan.get("carModel") or "").strip().lower()
    source_skin_id = str(plan.get("sourceSkinId") or "").strip().lower()
    exact: list[dict[str, Any]] = []
    related: list[dict[str, Any]] = []
    for entry in load_my_skins():
        if project_types.project_type_for_entry(entry) != project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
            continue
        details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
        migration = details.get("legacyMigration") if isinstance(details.get("legacyMigration"), dict) else {}
        if str(migration.get("source") or "") != "advanced_livery_package":
            continue
        existing_fingerprint = str(migration.get("contentFingerprint") or "").strip().lower()
        existing_car = str(migration.get("sourceCarModel") or entry.get("car_model") or "").strip().lower()
        existing_skin = str(migration.get("sourceSkinId") or "").strip().lower()
        if not existing_fingerprint:
            source_package = Path(str(migration.get("sourcePackage") or "")).expanduser()
            if source_package.exists():
                try:
                    with package_zip.package_root(source_package, get_package_import_temp_root(), allow_folder=True) as package_root:
                        existing_plan = advanced_to_official_import.inspect_package_root(package_root)
                    existing_fingerprint = str(existing_plan.get("contentFingerprint") or "").strip().lower()
                    existing_car = str(existing_plan.get("carModel") or existing_car).strip().lower()
                    existing_skin = str(existing_plan.get("sourceSkinId") or existing_skin).strip().lower()
                except Exception:
                    pass
        summary = {
            "key": str(entry.get("key") or ""),
            "skinId": str(entry.get("skin_id") or ""),
            "displayName": str(entry.get("display_name") or ""),
            "carModel": str(entry.get("car_model") or ""),
            "projectFolder": str(entry.get("project_folder") or entry.get("local_root") or ""),
            "contentFingerprint": existing_fingerprint,
        }
        if fingerprint and existing_fingerprint == fingerprint:
            exact.append(summary)
        elif car_model and source_skin_id and existing_car == car_model and existing_skin == source_skin_id:
            related.append(summary)
    return {
        "status": "exact" if exact else ("related_changed" if related else "new"),
        "exactMatches": exact,
        "relatedMatches": related,
        "existingEntry": exact[0] if exact else (related[0] if related else {}),
    }


def inspect_advanced_package_official_migration(
    package_path: str | Path,
    root: str | Path | None = None,
) -> dict[str, Any]:
    source = Path(package_path).expanduser()
    with package_zip.package_root(source, get_package_import_temp_root(), allow_folder=True) as package_root:
        plan = advanced_to_official_import.inspect_package_root(package_root)
        conversion_policy = advanced_to_official_import.official_conversion_policy(plan)
        resources = _advanced_package_official_resource_status(plan, root or get_default_root_folder())
        duplicate_status = _advanced_official_duplicate_status(plan)
        blockers = [*list(plan.get("blockers") or []), *list(resources.get("blockers") or [])]
        if not conversion_policy.get("allowed"):
            blockers.append(str(conversion_policy.get("reason") or "This Advanced package cannot be converted to Official."))
        return _json_ready({
            **plan,
            "packagePath": str(source),
            "ready": not blockers,
            "blockers": blockers,
            "officialResources": resources,
            "officialConversionPolicy": conversion_policy,
            "duplicateStatus": duplicate_status,
        })


def _advanced_official_variant(resources: dict[str, Any], requested: dict[str, Any] | None) -> dict[str, Any]:
    variants = resources.get("vehicleVariants") if isinstance(resources.get("vehicleVariants"), list) else []
    requested_data = requested if isinstance(requested, dict) else {}
    requested_id = str(requested_data.get("id") or requested_data.get("key") or "").strip()
    if requested_id and not any(str(row.get("id") or row.get("key") or "") == requested_id for row in variants if isinstance(row, dict)):
            raise ValueError(f"Requested Official vehicle variant is not available: {requested_id}")
    selected = advanced_to_official_import.select_compatible_official_variant(
        variants,
        requested_id=requested_id,
        default_id=str(resources.get("defaultVehicleVariantId") or ""),
    )
    if requested_id and not selected:
        raise ValueError(f"Requested Official vehicle variant is not compatible and no neutral fallback is available: {requested_id}")
    if requested_id:
        return selected
    selected = resources.get("defaultVehicleVariant") if isinstance(resources.get("defaultVehicleVariant"), dict) else {}
    if not selected:
        raise ValueError("No compatible Official vehicle variant is available")
    return selected


def _move_advanced_read_only_conversion_to_official_library(
    entry: dict[str, Any],
    project_folder: Path,
) -> tuple[dict[str, Any], Path]:
    details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
    descriptor = {
        "folderName": str(
            details.get("externalFolderName")
            or details.get("plannedExternalFolder")
            or details.get("sourceFolderName")
            or entry.get("skin_id")
            or "official_livery"
        ),
        "carGuid": str(entry.get("car_guid") or details.get("carGuid") or ""),
    }
    target = _official_external_local_root_for_descriptor(descriptor)
    if target.exists():
        raise FileExistsError(f"Read-only Official library folder already exists: {target}")
    target.parent.mkdir(parents=True, exist_ok=True)
    shutil.move(str(project_folder), str(target))
    try:
        for project_only_name in (
            official_external_projects.PROJECT_MANIFEST,
            official_external_project_assets.PROJECT_README,
        ):
            project_only_path = target / project_only_name
            if project_only_path.is_file():
                project_only_path.unlink()

        library_entry = advanced_to_official_import.as_read_only_official_library_entry(entry, target)
        library_details = dict(library_entry.get("official_external") or {}) if isinstance(library_entry.get("official_external"), dict) else {}
        source_descriptor_id = official_external_workflow.external_descriptor_id(
            descriptor.get("folderName"),
            descriptor.get("carGuid"),
        )
        if source_descriptor_id:
            library_details["sourceDescriptorId"] = source_descriptor_id
        library_entry["official_external"] = library_details
        library_entry = official_external_workflow.apply_library_entry_metadata(library_entry)
        return library_entry, target
    except Exception:
        if target.exists() and not project_folder.exists():
            shutil.move(str(target), str(project_folder))
        raise


def import_advanced_package_as_official(
    package_path: str | Path,
    root: str | Path | None = None,
    *,
    vehicle_variant: dict[str, Any] | None = None,
) -> dict[str, Any]:
    source = Path(package_path).expanduser()
    game_root = normalize_game_root(root or get_default_root_folder())
    with package_zip.package_root(source, get_package_import_temp_root(), allow_folder=True) as package_root:
        plan = advanced_to_official_import.inspect_package_root(package_root)
        conversion_policy = advanced_to_official_import.official_conversion_policy(plan)
        if not conversion_policy.get("allowed"):
            raise PermissionError(
                str(conversion_policy.get("reason") or "This Advanced package cannot be converted to Official.")
            )
        resources = _advanced_package_official_resource_status(plan, game_root)
        blockers = [*list(plan.get("blockers") or []), *list(resources.get("blockers") or [])]
        if blockers:
            raise ValueError("Legacy package is not ready for Official import: " + "; ".join(str(row) for row in blockers))
        duplicate_status = _advanced_official_duplicate_status(plan)
        if duplicate_status.get("status") == "exact":
            existing_summary = duplicate_status.get("existingEntry") if isinstance(duplicate_status.get("existingEntry"), dict) else {}
            existing_key = str(existing_summary.get("key") or "")
            existing_entry = next((row for row in load_my_skins() if str(row.get("key") or "") == existing_key), existing_summary)
            return {
                "status": "already_installed",
                "entry": existing_entry,
                "duplicateStatus": duplicate_status,
                "sourcePlan": plan,
            }
        variant = _advanced_official_variant(resources, vehicle_variant)
        created: dict[str, Any] | None = None
        project_folder: Path | None = None
        entry_key = ""
        try:
            created = create_new_official_external_project(
                game_root,
                livery_name=str(plan.get("displayName") or "Imported Official Livery"),
                car_model=str(plan.get("carModel") or ""),
                author=str(plan.get("author") or ""),
                description=str(plan.get("description") or ""),
                thumbnail=str(plan.get("thumbnailPath") or "") or None,
                vehicle_variant=variant,
                overwrite=False,
            )
            project_folder = Path(str(created.get("projectFolder") or ""))
            entry = created.get("entry") if isinstance(created.get("entry"), dict) else {}
            entry_key = str(entry.get("key") or "")
            if not project_folder.is_dir() or not entry_key:
                raise RuntimeError("Official project creation did not return a valid project")

            imported_slots: list[dict[str, Any]] = []
            for row in plan.get("slots") or []:
                if not isinstance(row, dict) or not row.get("ready"):
                    continue
                source_path = Path(str(row.get("sourcePath") or ""))
                target = project_folder / str(row.get("filename") or "")
                if row.get("sourceType") == "png":
                    shutil.copy2(source_path, target)
                    result = {"path": str(target), "reason": "copied project PNG", "skipped": False}
                elif row.get("sourceType") == "texture":
                    source_mips = source_path.with_suffix(".texturemips")
                    descriptor = parse_texture_descriptor(source_path)
                    block_bytes = infer_block_bytes_from_tiled_size(source_mips.stat().st_size, descriptor)
                    result = convert_texture_to_png(
                        source_path,
                        target,
                        overwrite=True,
                        create_dds=False,
                        format_name=advanced_to_official_import.legacy_texture_decode_format(
                            row.get("slotId"),
                            block_bytes,
                        ),
                    )
                else:
                    continue
                imported_slots.append({
                    "slotId": row.get("slotId") or "",
                    "sourceType": row.get("sourceType") or "",
                    "source": str(source_path),
                    "target": str(target),
                    "status": "imported" if not result.get("skipped") else "skipped",
                    "reason": result.get("reason") or "",
                    "decodeFormat": result.get("format") or "auto",
                })

            layer_studio_result = advanced_to_official_import.copy_layer_studio_sources(
                plan.get("layerStudioRoot") or "",
                project_folder / layer_studio.PROJECT_DIRNAME,
            )
            project_sources_result = advanced_to_official_import.copy_legacy_project_sources(
                plan.get("projectRoot") or "",
                project_folder,
            )
            detail_path = project_folder / "EXT_Skin_D.png"
            detail_neutrality = advanced_to_official_import.inspect_detail_map_neutrality(detail_path)
            detail_is_neutral_black = bool(detail_neutrality.get("neutral"))
            legacy_material = plan.get("legacyMaterial") if isinstance(plan.get("legacyMaterial"), dict) else {}
            if detail_is_neutral_black:
                material_migration = advanced_to_official_import.apply_baked_albedo_material(
                    project_folder / official_external_liveries.EXTERNAL_LIVERY_JSON,
                    legacy_material,
                )
                material_migration["status"] = "applied" if material_migration.get("changed") else "ready"
            elif not detail_is_neutral_black and legacy_material.get("officialChannels"):
                material_migration = advanced_to_official_import.apply_legacy_material_channels(
                    project_folder / official_external_liveries.EXTERNAL_LIVERY_JSON,
                    legacy_material,
                )
                material_migration["status"] = "applied" if material_migration.get("changed") else "ready"
                material_migration["detailTexturePath"] = str(detail_path)
            elif not detail_is_neutral_black:
                material_migration = {
                    "status": "manual_review",
                    "changed": False,
                    "reason": "Complex EXT_Skin_D uses non-black splat channels and its Legacy RGB/PBR values are unavailable",
                    "detailTexturePath": str(detail_path),
                    "fidelityWarnings": list(legacy_material.get("fidelityWarnings") or []),
                }
            else:
                material_migration = {
                    "status": "unavailable",
                    "changed": False,
                    "reason": str(legacy_material.get("reason") or "Legacy base material parameters are unavailable"),
                    "detailTexturePath": str(detail_path),
                }
            material_migration["detailIsNeutralBlack"] = detail_is_neutral_black
            material_migration["detailNeutrality"] = _json_ready(detail_neutrality)
            material_migration["sourceMaterial"] = str(legacy_material.get("path") or "")
            material_migration["sourceParameters"] = _json_ready(legacy_material.get("parameters") or {})
            migration = {
                "source": "advanced_livery_package",
                "sourcePackage": str(source),
                "sourceFormatVersion": plan.get("formatVersion"),
                "sourcePackageMode": plan.get("packageMode"),
                "sourceCarModel": plan.get("carModel") or "",
                "sourceSkinId": plan.get("sourceSkinId") or "",
                "contentFingerprint": plan.get("contentFingerprint") or "",
                "importedSlots": imported_slots,
                "droppedFeatures": plan.get("droppedFeatures") or [],
                "dependencyCounts": plan.get("dependencyCounts") or {},
                "manualTextureReview": bool(plan.get("manualTextureReview") or material_migration.get("fidelityWarnings")),
                "fidelityWarnings": list(material_migration.get("fidelityWarnings") or []),
                "layerStudio": layer_studio_result,
                "projectSources": project_sources_result,
                "material": material_migration,
                "variantFallback": {
                    "used": bool(variant.get("compatibilityFallbackFrom")),
                    "requestedVariantId": variant.get("compatibilityFallbackFrom") or "",
                    "selectedVariantId": variant.get("id") or variant.get("key") or "",
                    "reason": "Selected Advanced variant was replaced with a neutral Official-compatible SavedCar variant"
                    if variant.get("compatibilityFallbackFrom") else "",
                },
                "importedAt": datetime.now().isoformat(timespec="seconds"),
            }
            current, skins, index = _get_my_skin_entry(entry_key)
            read_only_conversion = str(plan.get("packageMode") or "").strip().lower() == "read_only"
            if read_only_conversion:
                current, project_folder = _move_advanced_read_only_conversion_to_official_library(current, project_folder)
                created["projectFolder"] = str(project_folder)
                created["libraryFolder"] = str(project_folder)
                created["manifestPath"] = ""
                created["readmePath"] = ""
            details = current.get("official_external") if isinstance(current.get("official_external"), dict) else {}
            details["legacyMigration"] = _json_ready(migration)
            current["official_external"] = details
            skins[index] = current
            save_my_skins(skins)
            refresh = (
                {
                    "skinKey": entry_key,
                    "projectFolder": str(project_folder),
                    "readOnly": True,
                    "assetStatus": official_external_project_assets.inspect_project(
                        project_folder,
                        material_batch=_official_external_entry_material_batch(current),
                    ),
                }
                if read_only_conversion
                else refresh_official_external_project_manifest(entry_key, car_guid=current.get("car_guid") or None)
            )
            validation = validate_official_external_livery(entry_key)
            local_validation = validation.get("validation") if isinstance(validation.get("validation"), dict) else {}
            if not local_validation.get("ready"):
                rows = local_validation.get("rows") if isinstance(local_validation.get("rows"), list) else []
                errors = [str(row.get("message") or row.get("title") or "validation error") for row in rows if isinstance(row, dict) and row.get("severity") == "error"]
                raise RuntimeError("Migrated Official livery validation failed: " + "; ".join(errors[:5]))
            return _json_ready({
                **created,
                "entry": current,
                "migration": migration,
                "refresh": refresh,
                "validation": validation,
                "sourcePlan": plan,
                "officialVariant": variant,
            })
        except Exception:
            if project_folder and project_folder.exists():
                shutil.rmtree(project_folder, ignore_errors=True)
            if entry_key:
                skins = [row for row in load_my_skins() if str(row.get("key") or "").strip().lower() != entry_key.lower()]
                save_my_skins(skins)
            raise


def get_pending_legacy_official_migration() -> dict[str, Any]:
    pending = legacy_official_migration.pending_manifest(get_portable_storage_root())
    if not pending:
        return {"pending": False, "queue": {}, "cleanup": {}}
    manifest = pending.get("manifest") if isinstance(pending.get("manifest"), dict) else {}
    queue = pending.get("queue") if isinstance(pending.get("queue"), dict) else {}
    cleanup = pending.get("cleanup") if isinstance(pending.get("cleanup"), dict) else {}
    migration_id = str(manifest.get("migrationId") or "").strip()
    dismissed_id = str(load_app_settings().get("legacy_official_migration_dismissed_id") or "").strip()
    game_root = str(get_default_root_folder() or "").strip()
    return _json_ready(
        {
            "pending": True,
            "manifestPath": pending.get("manifestPath") or "",
            "migrationId": migration_id,
            "dismissed": bool(migration_id and dismissed_id == migration_id),
            "source": manifest.get("source") or {},
            "target": manifest.get("target") or {},
            "queue": queue,
            "cleanup": cleanup,
            "gameRoot": game_root,
            "gameRootReady": bool(game_root and Path(game_root).is_dir()),
        }
    )


def set_legacy_official_migration_dismissed(migration_id: str, dismissed: bool = True) -> dict[str, Any]:
    clean_id = str(migration_id or "").strip()
    pending = legacy_official_migration.pending_manifest(get_portable_storage_root())
    manifest = pending.get("manifest") if isinstance(pending.get("manifest"), dict) else {}
    pending_id = str(manifest.get("migrationId") or "").strip()
    if not pending_id:
        raise FileNotFoundError("No pending legacy Official migration was found.")
    if not clean_id or clean_id != pending_id:
        raise ValueError("Pending legacy Official migration id does not match.")
    settings = load_app_settings()
    if dismissed:
        settings["legacy_official_migration_dismissed_id"] = pending_id
    elif str(settings.get("legacy_official_migration_dismissed_id") or "").strip() == pending_id:
        settings.pop("legacy_official_migration_dismissed_id", None)
    saved = save_app_settings(settings)
    return {
        "migrationId": pending_id,
        "dismissed": bool(dismissed),
        "settingsPath": str(saved),
    }


def _copy_migrated_livery_category(source_key: str, official_key: str) -> dict[str, Any]:
    source_state = load_livery_categories(workflow_scopes.SCOPE_ADVANCED)
    official_state = load_livery_categories(workflow_scopes.SCOPE_OFFICIAL)
    assignments = source_state.get("assignments") if isinstance(source_state.get("assignments"), dict) else {}
    source_category_id = next(
        (str(value or "") for key, value in assignments.items() if str(key or "").lower() == str(source_key or "").lower()),
        "",
    )
    assigned_category = ""
    if source_category_id:
        source_category = next(
            (row for row in source_state.get("categories", []) if isinstance(row, dict) and str(row.get("id") or "") == source_category_id),
            None,
        )
        category_name = str((source_category or {}).get("name") or "").strip()
        if category_name:
            official_category = next(
                (
                    row
                    for row in official_state.get("categories", [])
                    if isinstance(row, dict) and str(row.get("name") or "").strip().lower() == category_name.lower()
                ),
                None,
            )
            if official_category is None:
                created = create_livery_category(category_name, workflow_scopes.SCOPE_OFFICIAL)
                official_category = created.get("category") if isinstance(created.get("category"), dict) else {}
            assigned_category = str((official_category or {}).get("id") or "")
            if assigned_category:
                assign_livery_category(official_key, assigned_category, workflow_scopes.SCOPE_OFFICIAL)
    favorites = {
        str(value or "").strip().lower()
        for value in source_state.get("favorites", [])
        if str(value or "").strip()
    }
    favorite = str(source_key or "").strip().lower() in favorites
    if favorite:
        set_livery_favorite(official_key, True, workflow_scopes.SCOPE_OFFICIAL)
    return {"categoryId": assigned_category, "favorite": favorite}


def run_pending_legacy_official_migration(
    *,
    progress: Callable[[dict[str, Any]], None] | None = None,
) -> dict[str, Any]:
    pending = legacy_official_migration.pending_manifest(get_portable_storage_root())
    if not pending:
        raise FileNotFoundError("No pending legacy Official migration was found.")
    manifest_path = Path(str(pending.get("manifestPath") or "")).resolve()
    manifest = dict(pending.get("manifest") or {})
    migration_id = str(manifest.get("migrationId") or "").strip()
    settings = load_app_settings()
    if migration_id and str(settings.get("legacy_official_migration_dismissed_id") or "").strip() == migration_id:
        settings.pop("legacy_official_migration_dismissed_id", None)
        save_app_settings(settings)
    queue = dict(manifest.get("officialMigrationQueue") or {})
    items = [dict(item) for item in queue.get("items", []) if isinstance(item, dict)]
    game_root_text = str(get_default_root_folder() or "").strip()
    if not game_root_text or not Path(game_root_text).is_dir():
        queue["state"] = "blocked"
        queue["updatedAt"] = datetime.now().isoformat(timespec="seconds")
        queue["blockers"] = ["Configure the AC EVO game folder before continuing the Official migration."]
        manifest["officialMigrationQueue"] = queue
        legacy_official_migration.write_manifest(manifest_path, manifest)
        return _json_ready({"status": "blocked", "manifestPath": str(manifest_path), "queue": queue})

    game_root = normalize_game_root(game_root_text)
    official_resources = get_official_car_resources_status(game_root)
    if official_resources.get("ready") is not True:
        queue["state"] = "blocked"
        queue["updatedAt"] = datetime.now().isoformat(timespec="seconds")
        queue["blockers"] = ["Prepare the Official Preview Resources before continuing the Official migration."]
        manifest["officialMigrationQueue"] = queue
        legacy_official_migration.write_manifest(manifest_path, manifest)
        return _json_ready({
            "status": "blocked",
            "manifestPath": str(manifest_path),
            "queue": queue,
            "officialResources": official_resources,
        })

    queue["state"] = "running"
    queue["blockers"] = []
    queue["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    manifest["officialMigrationQueue"] = queue
    legacy_official_migration.write_manifest(manifest_path, manifest)
    temp_root = get_portable_storage_root() / "_tmp" / "legacy_official_migration" / str(manifest.get("migrationId") or "migration")
    temp_root.mkdir(parents=True, exist_ok=True)
    total = len(items)

    for index, item in enumerate(items, start=1):
        if str(item.get("status") or "") in legacy_official_migration.QUEUE_SUCCESS_STATES:
            continue
        source_key = str(item.get("sourceKey") or "").strip()
        label = str(item.get("displayName") or item.get("skinId") or source_key or f"Livery {index}")
        if progress:
            progress(
                {
                    "phase": "convert",
                    "percent": 5 + ((index - 1) / max(1, total)) * 90,
                    "current": index,
                    "total": total,
                    "label": label,
                    "message": f"Converting {label} to Official...",
                }
            )
        item["status"] = "running"
        item["attempts"] = int(item.get("attempts") or 0) + 1
        item["lastAttemptAt"] = datetime.now().isoformat(timespec="seconds")
        item.pop("lastError", None)
        package_path: Path | None = None
        try:
            source_entry = next(
                (row for row in load_my_skins() if str(row.get("key") or "").strip().lower() == source_key.lower()),
                None,
            )
            if source_entry is None:
                raise FileNotFoundError(f"Migrated Advanced livery was not found: {source_key}")
            package_mode = str(item.get("packageMode") or "read_only")
            export_result = export_my_skin_package(
                game_root,
                source_key,
                temp_root,
                package_mode,
                _legacy_migration_staging=True,
            )
            package_path = Path(str(export_result.get("packagePath") or export_result.get("packageRoot") or ""))
            inspection = inspect_advanced_package_official_migration(package_path, game_root)
            detected_losses = {
                str(row.get("field") or "")
                for row in inspection.get("droppedFeatures", [])
                if isinstance(row, dict) and str(row.get("field") or "")
            }
            for dependency_name, dependency_count in (inspection.get("dependencyCounts") or {}).items():
                if int(dependency_count or 0) > 0:
                    detected_losses.add(f"{dependency_name} ({int(dependency_count)} bundled)")
            item["advancedOnlyFeatures"] = sorted(
                {
                    *(str(value) for value in item.get("advancedOnlyFeatures", []) if str(value)),
                    *detected_losses,
                }
            )
            item["inspection"] = {
                "ready": bool(inspection.get("ready")),
                "blockers": list(inspection.get("blockers") or []),
                "warnings": list(inspection.get("warnings") or []),
                "droppedFeatures": list(inspection.get("droppedFeatures") or []),
            }
            if not inspection.get("ready"):
                item["status"] = "blocked"
                item["lastError"] = "; ".join(str(value) for value in inspection.get("blockers", [])) or "Official conversion preflight failed."
            else:
                converted = import_advanced_package_as_official(package_path, game_root)
                official_entry = converted.get("entry") if isinstance(converted.get("entry"), dict) else {}
                official_key = str(official_entry.get("key") or "")
                if not official_key:
                    raise RuntimeError("Official conversion did not return a livery entry.")
                item["status"] = "already_installed" if converted.get("status") == "already_installed" else "converted"
                item["officialKey"] = official_key
                item["officialProjectFolder"] = str(
                    converted.get("projectFolder")
                    or official_entry.get("project_folder")
                    or official_entry.get("local_root")
                    or ""
                )
                item["projectSources"] = _json_ready(
                    (converted.get("migration") or {}).get("projectSources")
                    if isinstance(converted.get("migration"), dict)
                    else {}
                )
                item["categoryTransfer"] = _copy_migrated_livery_category(source_key, official_key)
                item["completedAt"] = datetime.now().isoformat(timespec="seconds")
        except Exception as exc:
            item["status"] = "failed"
            item["lastError"] = str(exc)
        finally:
            if package_path and package_path.exists():
                try:
                    if package_path.is_dir():
                        shutil.rmtree(package_path)
                    else:
                        package_path.unlink()
                except OSError:
                    pass
            queue["items"] = items
            manifest["officialMigrationQueue"] = queue
            legacy_official_migration.refresh_queue_state(manifest)
            legacy_official_migration.write_manifest(manifest_path, manifest)

    shutil.rmtree(temp_root, ignore_errors=True)
    legacy_official_migration.refresh_queue_state(manifest)
    legacy_official_migration.write_manifest(manifest_path, manifest)
    final_queue = manifest.get("officialMigrationQueue") if isinstance(manifest.get("officialMigrationQueue"), dict) else {}
    if progress:
        progress(
            {
                "phase": "complete",
                "percent": 100,
                "current": total,
                "total": total,
                "message": "Official migration processing complete.",
            }
        )
    return _json_ready(
        {
            "status": final_queue.get("state") or "partial",
            "manifestPath": str(manifest_path),
            "queue": final_queue,
            "cleanup": manifest.get("advancedCopyCleanup") or {},
            "sourceInstallationModified": False,
            "gameFilesModified": False,
        }
    )


def cleanup_legacy_advanced_copy(decision: str, confirm_token: str = "") -> dict[str, Any]:
    pending = legacy_official_migration.pending_manifest(get_portable_storage_root())
    if not pending:
        raise FileNotFoundError("No completed legacy Official migration is awaiting cleanup.")
    return _json_ready(
        legacy_official_migration.cleanup_advanced_copy(
            str(pending.get("manifestPath") or ""),
            decision,
            load_skins=load_my_skins,
            save_skins=save_my_skins,
            confirm_token=confirm_token,
        )
    )


def _inspect_skin_package_dependency_status(package_root: Path, dependencies: dict[str, Any], skin: dict[str, Any], copied: list[Any]) -> dict[str, Any]:
    tyre_rows: list[dict[str, Any]] = []
    numberplate_rows: list[dict[str, Any]] = []
    driver_rows: list[dict[str, Any]] = []
    missing: list[dict[str, Any]] = []
    for dep in dependencies.get("tyreBrands", []) if isinstance(dependencies.get("tyreBrands"), list) else []:
        if not isinstance(dep, dict):
            continue
        brand_id = str(dep.get("id") or "").strip()
        package_rel = str(dep.get("packagePath") or "").strip()
        package_path = _skin_package_member_path(package_root, package_rel, "tyre brand dependency") if package_rel else None
        installed = bool(brand_id and _custom_tyre_brand_manifest_by_id(brand_id))
        embedded = bool(package_path and (package_path / "manifest.json").exists())
        row = {**dep, "installed": installed, "embedded": embedded, "packagePathResolved": str(package_path) if package_path else ""}
        if not installed and not embedded:
            missing.append({"kind": "tyreBrand", "id": brand_id, "reason": "not installed and no embedded dependency package"})
        tyre_rows.append(_json_ready(row))

    bake = skin.get("numberplateBake") if NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED and isinstance(skin.get("numberplateBake"), dict) else {}
    variant = skin.get("numberplateVariant") if isinstance(skin.get("numberplateVariant"), dict) else {}
    copied_paths = {normalize_game_rel_path(str(row.get("path") or "")).lower() for row in copied if isinstance(row, dict)}
    numberplate_dependencies = (
        dependencies.get("numberplateTemplates", [])
        if NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED and isinstance(dependencies.get("numberplateTemplates"), list)
        else []
    )
    for dep in numberplate_dependencies:
        if not isinstance(dep, dict):
            continue
        template_id = str(dep.get("id") or bake.get("template_id") or variant.get("template_id") or "").strip()
        baked_paths = skin_package_dependencies.baked_numberplate_paths(bake)
        missing_baked = [
            row for row in baked_paths
            if normalize_game_rel_path(str(row.get("path") or "")).lower() not in copied_paths
        ]
        installed = _skin_numberplate_template_available(template_id)
        row = {
            **dep,
            "installed": installed,
            "bakedOnly": bool(baked_paths and not installed),
            "bakedFileCount": len(baked_paths),
            "missingBakedFiles": missing_baked,
        }
        if missing_baked:
            missing.append({"kind": "numberplateBake", "id": template_id, "reason": "baked files missing", "missing": missing_baked})
        numberplate_rows.append(_json_ready(row))
    if NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED and not numberplate_rows:
        dep = skin_package_dependencies.numberplate_template_dependency(
            bake,
            variant,
            _skin_numberplate_template_available(str(bake.get("template_id") or variant.get("template_id") or "")),
        )
        if dep:
            numberplate_rows.append(_json_ready(dep))

    driver_deps = dependencies.get("driverProfiles") if isinstance(dependencies.get("driverProfiles"), list) else []
    if not driver_deps:
        driver_profile = skin.get("driverProfile") if isinstance(skin.get("driverProfile"), dict) else {}
        dep = _skin_package_driver_profile_dependency(driver_profile, str(skin.get("carModel") or "")) if driver_profile else None
        if dep:
            driver_deps = [dep]
    for dep in driver_deps:
        if not isinstance(dep, dict):
            continue
        profile_id = str(dep.get("id") or "").strip()
        car_model = str(dep.get("carModel") or skin.get("carModel") or "").strip()
        installed = bool(profile_id and driver_library.get_driver_profile(get_livery_library_root(), profile_id, car_model or None))
        package_rel = str(dep.get("packagePath") or "").strip()
        package_path = _skin_package_member_path(package_root, package_rel, "driver profile dependency") if package_rel else None
        embedded = bool(package_path and (package_path / "profile" / driver_library.DRIVER_MANIFEST_FILENAME).exists())
        project_embedded = bool(package_path and (package_path / "project" / driver_projects.DRIVER_PROJECT_FILENAME).exists())
        row = {
            **dep,
            "installed": installed,
            "embedded": embedded,
            "projectEmbedded": project_embedded,
            "packagePathResolved": str(package_path) if package_path else "",
        }
        if not installed and not row["embedded"]:
            missing.append({"kind": "driverProfile", "id": profile_id, "reason": "not installed and no embedded dependency package"})
        driver_rows.append(_json_ready(row))
    return {
        "tyreBrands": tyre_rows,
        "numberplateTemplates": numberplate_rows,
        "driverProfiles": driver_rows,
        "missing": _json_ready(missing),
        "missingCount": len(missing),
    }


def _build_my_skin_entry_from_manifest(root: Path, manifest: dict[str, Any], local_root: Path | None = None, active: bool = False) -> dict[str, Any]:
    skin = manifest.get("skin", {}) if isinstance(manifest.get("skin"), dict) else {}
    copied = manifest.get("copied", []) if isinstance(manifest.get("copied"), list) else []
    package_mode = str(manifest.get("packageMode") or "read_only").strip().lower()
    editable_project = package_mode == "editable_project"
    file_map: dict[str, Any] = {
        "cardesign": None,
        "design": None,
        "skin_folder": None,
        "material": None,
        "actor": None,
        "visual_preset": None,
        "mechanical_preset": None,
        "mechanical_presets": [],
        "carfinalstate": None,
        "carfinalstates": [],
        "thumbnail": None,
        "thumbnail_mips": None,
        "thumbnails": [],
        "thumbnail_mips_list": [],
        "thumbnail_png": None,
        "saved_car": None,
    }
    list_file_kinds = {"mechanical_presets", "carfinalstates", "thumbnails", "thumbnail_mips_list"}
    for row in copied:
        if not isinstance(row, dict):
            continue
        kind = str(row.get("kind", "")).strip()
        rel_path = norm_slashes(str(row.get("path", "")).strip())
        if not rel_path:
            continue
        if kind in {"thumbnail", "thumbnail_mips", "thumbnails", "thumbnail_mips_list"} and _is_generated_thumbnail_ref(rel_path):
            continue
        if kind == "loc":
            continue
        elif kind in list_file_kinds:
            rows = file_map.get(kind)
            if isinstance(rows, list) and rel_path not in rows:
                rows.append(rel_path)
        elif kind in file_map and not file_map[kind]:
            file_map[kind] = rel_path
    if file_map["mechanical_presets"] and not file_map["mechanical_preset"]:
        file_map["mechanical_preset"] = file_map["mechanical_presets"][0]
    if file_map["carfinalstates"] and not file_map["carfinalstate"]:
        file_map["carfinalstate"] = file_map["carfinalstates"][0]
    if file_map["thumbnails"] and not file_map["thumbnail"]:
        file_map["thumbnail"] = file_map["thumbnails"][0]
    if file_map["thumbnail_mips_list"] and not file_map["thumbnail_mips"]:
        file_map["thumbnail_mips"] = file_map["thumbnail_mips_list"][0]
    file_map = _strip_generated_thumbnail_file_refs(file_map)
    car_model = str(skin.get("carModel", "")).strip() or split_content_path(str(skin.get("carContentPath", "")))[-1]
    skin_id = str(skin.get("skinId", "")).strip() or "skin"
    numberplate_variant = _json_ready(skin.get("numberplateVariant") or {})
    numberplate_bake = _json_ready(skin.get("numberplateBake") or {})
    road_plate = _skin_package_road_plate_metadata(skin.get("roadPlate") or {})
    if not NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED:
        numberplate_variant = _sanitize_disabled_numberplate_variant_metadata(numberplate_variant, skin.get("startNumber", ""))
        numberplate_bake = {}
    if NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED and isinstance(numberplate_bake, dict):
        template_id = str(numberplate_bake.get("template_id") or (numberplate_variant if isinstance(numberplate_variant, dict) else {}).get("template_id") or "").strip()
        numberplate_bake = skin_package_dependencies.mark_baked_numberplate_import_state(numberplate_bake, _skin_numberplate_template_available(template_id))
    entry = {
        "key": str(skin.get("key", "")).strip().lower() or f"{car_model}:{skin_id}".lower(),
        "skin_id": skin_id,
        "display_name": str(skin.get("displayName", "")).strip() or skin_id,
        "car_model": car_model,
        "car_content_path": str(skin.get("carContentPath", "")).strip() or f"content\\cars\\{car_model}",
        "game_root": str(normalize_game_root(root)),
        "created_at": str(skin.get("createdAt", "")).strip() or datetime.now().isoformat(timespec="seconds"),
        "project_type": project_types.normalize_project_type(skin.get("projectType") or manifest.get("projectType")),
        "origin": skin_policy.ORIGIN_CREATED if editable_project else skin_policy.ORIGIN_PACKAGE_IMPORT,
        "project_state": skin_policy.PROJECT_STATE_EDITABLE if editable_project else skin_policy.PROJECT_STATE_NONE,
        "imported_at": datetime.now().isoformat(timespec="seconds"),
        "managed_by": MY_SKIN_MANAGED_BY_LIVERYLAB,
        "liverylab_managed": True,
        "start_number": str(skin.get("startNumber", "")).strip(),
        "visual_loc_key": str(skin.get("visualLocKey", "")).strip(),
        "active": bool(active),
        "local_root": str(local_root) if local_root else "",
        "files": file_map,
        "source_skin": _json_ready(skin.get("sourceSkin") or {}),
        "hidden": False,
    }
    if entry["project_type"] == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
        official_details = skin.get("officialExternal") if isinstance(skin.get("officialExternal"), dict) else {}
        entry["official_external"] = _json_ready(
            official_package_portability.sanitize_official_metadata(official_details)
        )
        if not editable_project:
            entry = official_external_workflow.apply_library_entry_metadata(entry)
    if isinstance(numberplate_variant, dict) and numberplate_variant:
        entry["numberplate_variant"] = numberplate_variant
    if isinstance(numberplate_bake, dict) and numberplate_bake:
        entry["numberplate_bake"] = numberplate_bake
    if isinstance(road_plate, dict) and road_plate:
        entry["road_plate"] = road_plate
    tyre_brand = skin.get("tyreBrand") if isinstance(skin.get("tyreBrand"), dict) else {}
    if tyre_brand:
        entry["tyre_brand"] = _json_ready(tyre_brand)
    driver_profile = skin.get("driverProfile") if isinstance(skin.get("driverProfile"), dict) else {}
    if _driver_profile_selection({"driver_profile": driver_profile}):
        entry["driver_profile"] = _json_ready(driver_profile)
    return entry


def _my_skin_same_identity(left: dict[str, Any], right: dict[str, Any]) -> bool:
    left_key = str(left.get("key") or "").strip().lower()
    right_key = str(right.get("key") or "").strip().lower()
    if left_key and right_key and left_key == right_key:
        return True
    left_car = str(left.get("car_model") or "").strip().lower()
    right_car = str(right.get("car_model") or "").strip().lower()
    left_skin = str(left.get("skin_id") or "").strip().lower()
    right_skin = str(right.get("skin_id") or "").strip().lower()
    return bool(left_car and right_car and left_skin and right_skin and left_car == right_car and left_skin == right_skin)


def _assert_skin_package_can_replace_existing(entry: dict[str, Any], skins: list[dict[str, Any]]) -> None:
    for item in skins:
        if not isinstance(item, dict) or not _my_skin_same_identity(item, entry):
            continue
        policy = skin_policy.policy_for_skin(item)
        if policy.get("editable"):
            display_name = str(item.get("display_name") or item.get("skin_id") or entry.get("skin_id") or "this skin").strip()
            car_model = str(item.get("car_model") or entry.get("car_model") or "").strip()
            raise ValueError(
                "Package import blocked to protect an editable project skin.\n"
                f"{display_name} already exists as an editable project"
                f"{f' for {car_model}' if car_model else ''}.\n"
                "Remove or rename the editable project before importing this package."
            )


def _rewrite_imported_livery_project(project_root: Path, entry: dict[str, Any], source_folder: str = "") -> None:
    project_file = project_root / "preview_project.json"
    source_text = str(source_folder or "").strip()
    target_text = str(project_root)
    source_variants = {
        value
        for value in {
            source_text,
            source_text.replace("\\", "/"),
            source_text.replace("/", "\\"),
        }
        if value
    }

    def project_file_path(*parts: str) -> str:
        return str(project_root.joinpath(*parts))

    def rewrite(value: Any) -> Any:
        if isinstance(value, dict):
            return {str(key): rewrite(val) for key, val in value.items()}
        if isinstance(value, list):
            return [rewrite(item) for item in value]
        if isinstance(value, str):
            next_value = value
            for source_variant in sorted(source_variants, key=len, reverse=True):
                if source_variant in next_value:
                    next_value = next_value.replace(source_variant, target_text)
            return next_value
        return value

    for json_path in project_root.rglob("*.json"):
        if not json_path.is_file():
            continue
        try:
            original = json.loads(json_path.read_text(encoding="utf-8-sig"))
        except Exception:
            continue
        rewritten = rewrite(original)
        if rewritten != original:
            json_path.write_text(json.dumps(_json_ready(rewritten), indent=2, ensure_ascii=False), encoding="utf-8")

    if not project_file.exists() or not project_file.is_file():
        return
    try:
        data = json.loads(project_file.read_text(encoding="utf-8"))
    except Exception:
        return
    if not isinstance(data, dict):
        return
    data["projectPath"] = str(project_file)
    data["projectFolder"] = target_text
    data["texturePath"] = project_file_path("EXT_SKIN_C.png")
    data["liveTexturePath"] = project_file_path("EXT_SKIN_C.png")
    if (project_root / "EXT_Banner_C.png").is_file():
        data["bannerTexturePath"] = project_file_path("EXT_Banner_C.png")
        data["liveBannerTexturePath"] = project_file_path("EXT_Banner_C.png")
    if (project_root / "EXT_Skin_D.png").is_file():
        data["detailTexturePath"] = project_file_path("EXT_Skin_D.png")
        data["liveDetailTexturePath"] = project_file_path("EXT_Skin_D.png")
    if (project_root / uv_templates.UV_TEMPLATE_NAME).is_file():
        data["uvTemplateName"] = uv_templates.UV_TEMPLATE_NAME
        data["uvTemplatePath"] = project_file_path(uv_templates.UV_TEMPLATE_NAME)
    if (project_root / uv_templates.PARTS_UV_TEMPLATE_NAME).is_file():
        data["partsUvTemplateName"] = uv_templates.PARTS_UV_TEMPLATE_NAME
        data["partsUvTemplatePath"] = project_file_path(uv_templates.PARTS_UV_TEMPLATE_NAME)
    if (project_root / uv_templates.VARIANT_UV_TEMPLATE_NAME).is_file():
        data["variantUvTemplateName"] = uv_templates.VARIANT_UV_TEMPLATE_NAME
        data["variantUvTemplatePath"] = project_file_path(uv_templates.VARIANT_UV_TEMPLATE_NAME)
    if (project_root / uv_templates.BANNER_UV_TEMPLATE_NAME).is_file():
        data["bannerUvTemplateName"] = uv_templates.BANNER_UV_TEMPLATE_NAME
        data["bannerUvTemplatePath"] = project_file_path(uv_templates.BANNER_UV_TEMPLATE_NAME)
    data["skinKey"] = str(entry.get("key") or data.get("skinKey") or "")
    data["skinId"] = str(entry.get("skin_id") or data.get("skinId") or "")
    data["projectType"] = project_types.project_type_for_entry(entry)
    data["displayName"] = str(entry.get("display_name") or data.get("displayName") or "")
    data["carModel"] = str(entry.get("car_model") or data.get("carModel") or "")
    data["updatedAt"] = datetime.now().isoformat(timespec="seconds")
    project_file.write_text(json.dumps(_json_ready(data), indent=2, ensure_ascii=False), encoding="utf-8")


def install_skin_package(root: Path, package_dir: Path) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    source_package = Path(package_dir).expanduser()
    with package_zip.package_root(source_package, get_package_import_temp_root(), allow_folder=True) as package_root:
        return _install_skin_package_root(game_root, package_root, source_package)


def import_official_package_as_advanced(
    package_path: str | Path,
    root: str | Path | None = None,
) -> dict[str, Any]:
    source = Path(package_path).expanduser()
    game_root = normalize_game_root(root or get_default_root_folder())
    require_advanced_content_setup_ready(game_root)
    cleanup_snapshot: dict[str, Any] | None = None
    project_folder: Path | None = None
    project_folder_preexisted = False
    with package_zip.package_root(source, get_package_import_temp_root(), allow_folder=True) as package_root:
        info = _inspect_skin_package_root(package_root, source)
        manifest = info.get("manifest") if isinstance(info.get("manifest"), dict) else {}
        skin_manifest = dict(manifest.get("skin")) if isinstance(manifest.get("skin"), dict) else {}
        package_project_type = project_types.normalize_project_type(
            skin_manifest.get("projectType") or manifest.get("projectType")
        )
        if package_project_type != project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
            raise ValueError("Official-to-Advanced conversion requires an Official livery package")

        package_mode = str(manifest.get("packageMode") or "read_only").strip().lower()
        conversion_policy = official_to_advanced_import.conversion_policy(package_mode)
        if not conversion_policy.get("allowed"):
            raise PermissionError(str(conversion_policy.get("reason") or "This Official package cannot be converted to Advanced."))
        editable_project = package_mode == "editable_project"
        official_info = manifest.get("officialExternal") if isinstance(manifest.get("officialExternal"), dict) else {}
        content_root_text = str(official_info.get("contentRoot") or "").strip()
        if content_root_text:
            payload_root = _skin_package_member_path(package_root, content_root_text, "Official package content root")
        elif editable_project:
            project_info = manifest.get("project") if isinstance(manifest.get("project"), dict) else {}
            payload_root = _skin_package_member_path(
                package_root,
                str(project_info.get("path") or "project"),
                "editable Official project",
            )
        else:
            raise ValueError("Read-only Official package is missing its content root")

        validation = official_package_validation.validate_payload(
            payload_root,
            skin_manifest,
            allow_linked_saved_car=False,
        )
        if not validation.get("ready"):
            errors = [str(value) for value in validation.get("errors") or [] if str(value).strip()]
            raise ValueError("Official package validation failed: " + "; ".join(errors[:8]))
        saved_car = validation.get("savedCar") if isinstance(validation.get("savedCar"), dict) else {}
        saved_car_path = Path(str(saved_car.get("path") or ""))
        if saved_car_path.is_file():
            saved_car = {
                **official_saved_car_state.inspect_saved_car_file(saved_car_path),
                **saved_car,
            }
        visual_ref = official_to_advanced_import.safe_advanced_content_ref(
            saved_car.get("visualPreset"),
            "SavedCar visual preset",
        )
        if not visual_ref:
            raise ValueError("Official package SavedCar does not identify a visual preset")
        visual_template = resolve_content_path(game_root, visual_ref)
        if not visual_template.is_file():
            raise FileNotFoundError(f"Official package vehicle visual preset is not available in Advanced content: {visual_ref}")
        visual_info = extract_visualpreset_info(visual_template.read_bytes())
        cardesign_ref = official_to_advanced_import.safe_advanced_content_ref(
            visual_info.get("cardesign"),
            "vehicle cardesign",
        )
        template_cardesign = resolve_content_path(game_root, cardesign_ref)
        if not template_cardesign.is_file():
            raise FileNotFoundError(f"Official package vehicle cardesign is not available in Advanced content: {cardesign_ref}")
        design_ref, _rim_ref = extract_cardesign_info(template_cardesign.read_bytes())
        if not design_ref:
            raise ValueError("Official package vehicle cardesign does not identify a design")
        design_ref = official_to_advanced_import.safe_advanced_content_ref(design_ref, "vehicle design")
        template_design = resolve_content_path(game_root, design_ref)
        if not template_design.is_file():
            raise FileNotFoundError(f"Official package vehicle design is not available in Advanced content: {design_ref}")
        template_info = extract_design_info(template_cardesign.read_bytes(), template_design.read_bytes())
        expected_car = str(saved_car.get("carModel") or skin_manifest.get("carModel") or "").strip().lower()
        actual_car = split_content_path(str(template_info.car_content_path or ""))[-1].strip().lower()
        if expected_car and actual_car != expected_car:
            raise ValueError(
                f"Official package vehicle template mismatch: expected {expected_car}, resolved {actual_car or 'unknown'}"
            )

        mechanical_ref = (
            official_to_advanced_import.safe_advanced_content_ref(
                saved_car.get("mechanicalPreset"),
                "SavedCar mechanical preset",
            )
            if str(saved_car.get("mechanicalPreset") or "").strip()
            else ""
        )
        mechanical_template = resolve_content_path(game_root, mechanical_ref) if mechanical_ref else None
        if mechanical_template is not None and not mechanical_template.is_file():
            mechanical_template = None
        display_name = str(skin_manifest.get("displayName") or skin_manifest.get("name") or "Imported Official Livery").strip()
        requested_skin_id = livery_identity.technical_skin_id(
            skin_manifest.get("skinId") or skin_manifest.get("externalFolderName") or display_name,
            "official_import",
        )
        conflict = _skin_id_conflict_details(game_root, template_info, requested_skin_id, create_visual_preset=True)
        if conflict.get("conflict"):
            requested_skin_id = _suggest_available_skin_id(
                game_root,
                template_info,
                requested_skin_id,
                create_visual_preset=True,
            )
        cleanup_snapshot = create_skin_cleanup_snapshot(
            game_root,
            template_cardesign,
            template_design,
            requested_skin_id,
        )
        project_folder = (
            get_livery_projects_root()
            / preview3d.safe_segment(actual_car, "car")
            / preview3d.safe_segment(requested_skin_id, "skin")
        )
        project_folder_preexisted = project_folder.exists()
        try:
            created = create_skin(
                game_root,
                template_cardesign,
                template_design,
                requested_skin_id,
                display_name,
                overwrite=False,
                copy_source_skin=True,
                patch_actor=True,
                create_visual_preset=True,
                visual_template=visual_template,
                mechanical_template=mechanical_template,
                create_carfinalstate=True,
                author=str(skin_manifest.get("author") or ""),
                description=str(skin_manifest.get("description") or ""),
            )
            entry = created.my_skin_entry if isinstance(created.my_skin_entry, dict) else {}
            if not entry or not str(entry.get("key") or "").strip():
                raise RuntimeError("Advanced project creation did not return a valid My Liveries entry")
            prepared = prepare_livery_preview_project(str(entry.get("key") or ""), game_root)
            project_folder = Path(str(prepared.get("projectFolder") or ""))
            if not project_folder.is_dir():
                raise RuntimeError("Advanced project creation did not return a valid LiveryProject folder")
            artwork = official_to_advanced_import.copy_baked_artwork(payload_root, project_folder)
            layer_sources: dict[str, Any] = {"copied": False, "fileCount": 0}
            if editable_project:
                project_info = manifest.get("project") if isinstance(manifest.get("project"), dict) else {}
                project_source = _skin_package_member_path(
                    package_root,
                    str(project_info.get("path") or "project"),
                    "editable Official project",
                )
                layer_sources = official_to_advanced_import.copy_editable_layer_studio_sources(
                    project_source,
                    project_folder,
                )
            derived_metadata = official_to_advanced_import.metadata_for_conversion(
                package_mode=package_mode,
                source_skin=skin_manifest,
                source_package=source,
            )
            if editable_project:
                entry[official_to_advanced_import.DERIVED_METADATA_KEY] = derived_metadata
            else:
                entry = official_to_advanced_import.apply_read_only_entry_policy(
                    entry,
                    source_skin=skin_manifest,
                    source_package=source,
                )
            entry["game_root"] = str(game_root)
            entry["imported_at"] = datetime.now().isoformat(timespec="seconds")
            entry["source_package"] = source.name
            _save_my_skin_entry(entry)
            created.my_skin_entry = entry
            return _json_ready({
                "status": "converted",
                "packagePath": str(source),
                "packageMode": package_mode,
                "entry": entry,
                "mySkinEntry": entry,
                "projectFolder": str(project_folder),
                "localRoot": str(entry.get("local_root") or ""),
                "conversionPolicy": conversion_policy,
                "conversion": {
                    "artwork": artwork,
                    "layerStudioSources": layer_sources,
                    "savedCar": saved_car,
                    "metadata": derived_metadata,
                },
            })
        except Exception:
            cleanup_failed_create_skin_outputs(cleanup_snapshot)
            if project_folder and project_folder.exists() and not project_folder_preexisted:
                shutil.rmtree(project_folder, ignore_errors=True)
            raise


def import_livery_package_smart(
    root: str | Path,
    package_path: str | Path,
    workflow_scope: str = "advanced",
) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    source = Path(package_path).expanduser()
    scope = workflow_scopes.normalize_scope(workflow_scope, default=workflow_scopes.SCOPE_ADVANCED)
    with package_zip.package_root(source, get_package_import_temp_root(), allow_folder=True) as package_root:
        package_plan = advanced_to_official_import.inspect_package_root(package_root)
    package_type = str(package_plan.get("projectType") or "").strip().lower()
    advanced_package = package_type in advanced_to_official_import.ADVANCED_PROJECT_TYPES
    official_package = package_type == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY
    if not advanced_package and not official_package:
        raise ValueError(f"Unsupported livery package project type: {package_type or 'unknown'}")

    if advanced_package and scope == workflow_scopes.SCOPE_OFFICIAL:
        result = import_advanced_package_as_official(source, game_root)
        entry = result.get("entry") if isinstance(result.get("entry"), dict) else {}
        return _json_ready({
            **result,
            "route": "advanced_to_official",
            "targetScope": workflow_scopes.SCOPE_OFFICIAL,
            "packagePlan": package_plan,
            "mySkinEntry": entry,
        })

    if official_package and scope == workflow_scopes.SCOPE_ADVANCED:
        result = import_official_package_as_advanced(source, game_root)
        entry = result.get("entry") if isinstance(result.get("entry"), dict) else {}
        return _json_ready({
            **result,
            "route": "official_to_advanced",
            "targetScope": workflow_scopes.SCOPE_ADVANCED,
            "packagePlan": package_plan,
            "mySkinEntry": entry,
        })

    result = install_skin_package(game_root, source)
    entry = result.get("mySkinEntry") if isinstance(result.get("mySkinEntry"), dict) else {}
    target_scope = workflow_scopes.SCOPE_OFFICIAL if official_package else workflow_scopes.SCOPE_ADVANCED
    return _json_ready({
        **result,
        "route": "official_package_import" if official_package else "advanced_package_import",
        "targetScope": target_scope,
        "packagePlan": package_plan,
        "mySkinEntry": entry,
    })


def _normalize_official_read_only_thumbnail_file(entry: dict[str, Any], deployed_local_root: Path) -> None:
    files = dict(entry.get("files") or {}) if isinstance(entry.get("files"), dict) else {}
    installed_thumbnail = deployed_local_root / official_package_validation.THUMBNAIL_FILENAME
    if installed_thumbnail.is_file():
        files["thumbnail_png"] = official_package_validation.THUMBNAIL_FILENAME
    else:
        files.pop("thumbnail_png", None)
    entry["files"] = files


def _install_skin_package_root(game_root: Path, package_root: Path, source_package: Path) -> dict[str, Any]:
    info = _inspect_skin_package_root(package_root, source_package)
    manifest = info["manifest"]
    package_root = Path(info["packageRoot"])
    copied_rows = info.get("copied", []) or []
    package_mode = str(manifest.get("packageMode") or "read_only").strip().lower()
    editable_project = package_mode == "editable_project"
    skin_manifest = dict(manifest.get("skin")) if isinstance(manifest.get("skin"), dict) else {}
    package_project_type = project_types.normalize_project_type(
        skin_manifest.get("projectType") or manifest.get("projectType")
    )
    official_package_validation_result: dict[str, Any] = {}
    if package_project_type == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY:
        official_package_info = manifest.get("officialExternal") if isinstance(manifest.get("officialExternal"), dict) else {}
        content_root_text = str(official_package_info.get("contentRoot") or "").strip()
        if content_root_text:
            official_payload_root = _skin_package_member_path(
                package_root,
                content_root_text,
                "Official package content root",
            )
        elif editable_project:
            project_info = manifest.get("project") if isinstance(manifest.get("project"), dict) else {}
            official_payload_root = _skin_package_member_path(
                package_root,
                str(project_info.get("path") or "project"),
                "editable Official project",
            )
        else:
            raise ValueError("Read-only Official package is missing its content root")
        official_package_validation_result = official_package_validation.validate_payload(
            official_payload_root,
            skin_manifest,
            allow_linked_saved_car=False,
        )
        if not official_package_validation_result.get("ready"):
            errors = [
                str(value)
                for value in official_package_validation_result.get("errors") or []
                if str(value).strip()
            ]
            raise ValueError("Official package validation failed: " + "; ".join(errors[:8]))
        saved_car = (
            official_package_validation_result.get("savedCar")
            if isinstance(official_package_validation_result.get("savedCar"), dict)
            else {}
        )
        skin_manifest["officialExternal"] = _official_package_embedded_saved_car_metadata(
            skin_manifest.get("officialExternal") or {},
            saved_car,
        )
        if saved_car.get("carGuid"):
            skin_manifest["carGuid"] = saved_car["carGuid"]
        manifest["skin"] = skin_manifest
        if not editable_project:
            _content_root_norm, content_root_disk = _skin_package_rel_disk_path(
                content_root_text,
                "Official package content root",
            )
            validated_copied_rows: list[dict[str, Any]] = []
            for row in official_package_validation_result.get("copyPlan") or []:
                if not isinstance(row, dict):
                    continue
                source = Path(str(row.get("source") or "")).resolve()
                relative = source.relative_to(official_payload_root.resolve())
                validated_copied_rows.append({
                    "kind": row.get("kind") or "official_external_file",
                    "path": norm_slashes(str(content_root_disk / relative)),
                    "externalLiveryPath": norm_slashes(str(relative)),
                    "type": "file",
                    "size": source.stat().st_size,
                    "slot": row.get("slot") or "",
                    "label": row.get("label") or relative.name,
                })
            saved_car_source = Path(str(saved_car.get("path") or "")).resolve()
            saved_car_relative = saved_car_source.relative_to(official_payload_root.resolve())
            validated_copied_rows.append({
                "kind": "saved_car",
                "path": norm_slashes(str(content_root_disk / saved_car_relative)),
                "externalLiveryPath": norm_slashes(str(saved_car_relative)),
                "type": "file",
                "size": saved_car_source.stat().st_size,
                "sha256": saved_car.get("sha256") or "",
                "label": "Embedded SavedCar",
            })
            copied_rows = validated_copied_rows
            manifest["copied"] = copied_rows
    entry = _build_my_skin_entry_from_manifest(game_root, manifest, active=False)
    official_read_only = (
        not editable_project
        and project_types.project_type_for_entry(entry) == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY
    )
    official_editable_project = (
        editable_project
        and project_types.project_type_for_entry(entry) == project_types.PROJECT_TYPE_OFFICIAL_EXTERNAL_LIVERY
    )
    local_root: Path | None = None
    if not official_editable_project:
        if official_read_only:
            skin_manifest = manifest.get("skin") if isinstance(manifest.get("skin"), dict) else {}
            descriptor = {
                "folderName": str(skin_manifest.get("externalFolderName") or entry.get("skin_id") or "official_livery"),
                "carGuid": str(skin_manifest.get("carGuid") or entry.get("car_guid") or ""),
            }
            local_root = _official_external_local_root_for_descriptor(descriptor)
        else:
            local_root = get_local_skin_root(
                game_root,
                str(entry.get("car_content_path", "")).strip() or f"content\\cars\\{entry.get('car_model', '')}",
                str(entry.get("skin_id", "")).strip() or "skin",
            )
        entry = _build_my_skin_entry_from_manifest(game_root, manifest, local_root=local_root, active=False)
    skins = load_my_skins()
    _assert_skin_package_can_replace_existing(entry, skins)
    project_import: dict[str, Any] = {"status": "skipped", "reason": "read-only package"}
    if editable_project:
        project_info = manifest.get("project") if isinstance(manifest.get("project"), dict) else {}
        project_rel = str(project_info.get("path") or "project").strip()
        project_source = _skin_package_member_path(package_root, project_rel, "editable livery project")
        if not project_source.exists() or not project_source.is_dir():
            raise FileNotFoundError("Editable project package is missing its project folder")
        if official_editable_project:
            skin_manifest = dict(manifest.get("skin")) if isinstance(manifest.get("skin"), dict) else {}
            source_details = official_package_portability.sanitize_official_metadata(
                skin_manifest.get("officialExternal") if isinstance(skin_manifest.get("officialExternal"), dict) else {}
            )
            skin_manifest["officialExternal"] = source_details
            external_folder_name = str(
                skin_manifest.get("externalFolderName")
                or source_details.get("externalFolderName")
                or source_details.get("plannedExternalFolder")
                or source_details.get("sourceFolderName")
                or entry.get("skin_id")
                or ""
            ).strip()
            project_target = official_external_projects.new_project_folder(
                get_official_livery_projects_root(create=True),
                str(entry.get("car_model") or ""),
                str(entry.get("display_name") or entry.get("skin_id") or "Imported Official Livery"),
                external_folder_name,
                str(entry.get("skin_id") or ""),
            )
        else:
            project_target = _livery_project_folder_for_entry(entry)
        if project_target.exists() and any(project_target.iterdir()):
            raise FileExistsError(f"Editable LiveryProject already exists: {project_target}")
        try:
            copy_package_import_tree(project_source, project_target, "editable livery project target")
            _rewrite_imported_livery_project(project_target, entry, str(project_info.get("sourceFolder") or ""))
            if official_editable_project:
                embedded_saved_car = (
                    official_package_validation_result.get("savedCar")
                    if isinstance(official_package_validation_result.get("savedCar"), dict)
                    else {}
                )
                embedded_saved_car_source = Path(str(embedded_saved_car.get("path") or "")).expanduser()
                embedded_saved_car_target = project_target / "SavedCar" / embedded_saved_car_source.name
                if not embedded_saved_car_target.is_file():
                    copy_package_import_file(
                        embedded_saved_car_source,
                        embedded_saved_car_target,
                        "embedded Official SavedCar",
                    )
                imported_official = official_external_projects.import_editable_project(
                    project_target,
                    skin_manifest,
                    game_root=game_root,
                    preview_assets_root=None,
                )
                imported_entry = imported_official.get("entry") if isinstance(imported_official.get("entry"), dict) else {}
                if not imported_entry:
                    raise RuntimeError("Editable Official package did not produce a My Liveries project entry")
                entry = imported_entry
                local_root = project_target
                _write_skin_metadata(entry)
        except Exception:
            shutil.rmtree(project_target, ignore_errors=True)
            raise
        project_import = {
            "status": "imported",
            "source": str(project_source),
            "target": str(project_target),
            "fileCount": sum(1 for child in project_target.rglob("*") if child.is_file()),
        }
    imported: list[dict[str, Any]] = []
    missing_sources: list[dict[str, Any]] = []
    dependency_imports = _install_skin_package_dependencies(package_root, manifest.get("dependencies") if isinstance(manifest.get("dependencies"), dict) else {})

    install_rows = [] if official_editable_project else copied_rows
    official_content_disk: Path | None = None
    if official_read_only:
        official_package = manifest.get("officialExternal") if isinstance(manifest.get("officialExternal"), dict) else {}
        content_root_text = str(official_package.get("contentRoot") or "").strip()
        if not content_root_text:
            raise ValueError("Read-only Official package is missing its content root")
        _content_rel, official_content_disk = _skin_package_rel_disk_path(content_root_text, "Official package content root")
    for row in install_rows:
        if not isinstance(row, dict):
            continue
        kind = str(row.get("kind", "")).strip() or "file"
        rel_path, rel_disk = _skin_package_rel_disk_path(row.get("path"), f"{kind} path")
        if "\\numberplates\\liverylab_baked\\" in f"\\{normalize_game_rel_path(rel_path).lower()}":
            continue
        if kind in {"thumbnail", "thumbnail_mips", "thumbnails", "thumbnail_mips_list"} and _is_generated_thumbnail_ref(rel_path):
            continue
        source = package_root / rel_disk
        if official_content_disk is not None:
            try:
                target_rel = rel_disk.relative_to(official_content_disk)
            except ValueError as exc:
                raise ValueError(f"Official package file is outside its content root: {rel_path}") from exc
            target = local_root / target_rel
        else:
            target = local_root / rel_disk
        if not source.exists():
            missing_sources.append({"kind": kind, "path": rel_path})
            continue
        if source.is_dir():
            count = 0
            for child in source.rglob("*"):
                if not child.is_file():
                    continue
                child_rel = child.relative_to(source)
                child_target = target / child_rel
                copy_package_import_file(child, child_target, "skin package file")
                count += 1
            imported.append({"kind": kind, "path": rel_path, "type": "directory", "files": count})
            continue
        copy_package_import_file(source, target, "skin package file")
        imported.append({"kind": kind, "path": rel_path, "type": "file"})

    if official_read_only and local_root is not None:
        deployed_local_root = local_root
        if not deployed_local_root.is_dir():
            raise FileNotFoundError(f"Imported Official package content folder is missing: {deployed_local_root}")
        entry["local_root"] = str(deployed_local_root)
        _normalize_official_read_only_thumbnail_file(entry, deployed_local_root)
        entry["active"] = False
        details = dict(entry.get("official_external") or {}) if isinstance(entry.get("official_external"), dict) else {}
        details["deploymentStatus"] = "package_not_deployed"
        details["deployed"] = False
        details["savedCarFound"] = False
        details["savedCarPaths"] = []
        details["deploymentTargetFolder"] = ""
        generated = details.get("generatedSavedCar") if isinstance(details.get("generatedSavedCar"), dict) else {}
        saved_car_name = Path(str(generated.get("filename") or "")).name
        if saved_car_name:
            generated["relativePath"] = norm_slashes(str(Path("SavedCar") / saved_car_name))
            generated["path"] = str(deployed_local_root / "SavedCar" / saved_car_name)
            generated["deploymentStatus"] = "prepared_local" if (deployed_local_root / "SavedCar" / saved_car_name).is_file() else "missing_local"
            generated["deployedPath"] = ""
            generated.pop("deployedAt", None)
            details["generatedSavedCar"] = generated
            entry.setdefault("files", {})["saved_car"] = generated["relativePath"]
        entry["official_external"] = details
        entry = official_external_workflow.apply_library_entry_metadata(entry)

    replaced = False
    for index, item in enumerate(skins):
        if str(item.get("key", "")).lower() == entry["key"]:
            skins[index] = entry
            replaced = True
            break
    if not replaced:
        skins.append(entry)
    saved = save_my_skins(skins)
    safety_refresh = {
        "attempted": False,
        "status": "skipped",
        "reason": "local library import only",
        "workflow": "install_skin_package",
    }
    return {
        "packageRoot": package_root,
        "packagePath": source_package,
        "manifestPath": info["manifestPath"],
        "localRoot": local_root,
        "installedCount": len(imported),
        "importedCount": len(imported),
        "missingSourceCount": len(missing_sources),
        "backupCount": 0,
        "installed": imported,
        "imported": imported,
        "missingSources": missing_sources,
        "backups": [],
        "patchedCount": 0,
        "patched": [],
        "dependencies": dependency_imports,
        "packageMode": package_mode,
        "project": project_import,
        "projectImported": project_import.get("status") == "imported",
        "mySkinEntry": entry,
        "settingsPath": saved,
        "safetyRefresh": safety_refresh,
        "writer": {},
    }


def _import_driver_profile_package(package_root: Path, dep: dict[str, Any]) -> dict[str, Any]:
    profile_id = str(dep.get("id") or "").strip()
    car_model = str(dep.get("carModel") or "").strip()
    if not profile_id or not car_model:
        return {"status": "missing", "kind": "driverProfile", "id": profile_id, "carModel": car_model, "reason": "driver profile id or car model missing"}
    if not driver_library.is_driver_car_supported(car_model):
        return {"status": "missing", "kind": "driverProfile", "id": profile_id, "carModel": car_model, "reason": driver_library.DRIVER_SUPPORT_MESSAGE}

    existing = driver_library.get_driver_profile(get_livery_library_root(), profile_id, car_model or None)
    if existing:
        return {"status": "already_installed", "kind": "driverProfile", "id": profile_id, "carModel": car_model}

    package_rel = str(dep.get("packagePath") or "").strip()
    if not package_rel:
        return {"status": "missing", "kind": "driverProfile", "id": profile_id, "carModel": car_model, "reason": "dependency package path missing"}
    dependency_root = _skin_package_member_path(package_root, package_rel, "driver profile dependency")
    profile_source = dependency_root / "profile"
    profile_manifest = profile_source / driver_library.DRIVER_MANIFEST_FILENAME
    if not profile_manifest.exists():
        return {
            "status": "missing",
            "kind": "driverProfile",
            "id": profile_id,
            "carModel": car_model,
            "path": package_rel,
            "reason": "embedded driver profile manifest missing",
        }

    target_profile_root = _driver_profile_package_target(car_model, profile_id)
    if target_profile_root.exists():
        return {
            "status": "missing",
            "kind": "driverProfile",
            "id": profile_id,
            "carModel": car_model,
            "reason": "target driver profile folder already exists but is not a valid installed profile",
            "target": str(target_profile_root),
        }
    copy_package_import_tree(profile_source, target_profile_root, "driver profile dependency target")

    project_import: dict[str, Any] = {"status": "missing", "reason": "embedded driver project missing"}
    project_source = dependency_root / "project"
    target_project_root: Path | None = None
    if (project_source / driver_projects.DRIVER_PROJECT_FILENAME).exists():
        target_project_root = _driver_project_package_target(car_model, profile_id)
        if target_project_root.exists():
            project_import = {"status": "already_installed", "target": str(target_project_root)}
        else:
            copy_package_import_tree(project_source, target_project_root, "driver project dependency target")
            _rewrite_imported_driver_project_manifest(target_project_root, target_profile_root)
            project_import = {
                "status": "imported",
                "target": str(target_project_root),
                "fileCount": sum(1 for child in target_project_root.rglob("*") if child.is_file()),
            }

    _rewrite_imported_driver_profile_manifest(target_profile_root, target_project_root)
    return {
        "status": "imported",
        "kind": "driverProfile",
        "id": profile_id,
        "carModel": car_model,
        "target": str(target_profile_root),
        "fileCount": sum(1 for child in target_profile_root.rglob("*") if child.is_file()),
        "project": project_import,
    }


def _install_skin_package_dependencies(package_root: Path, dependencies: dict[str, Any]) -> dict[str, Any]:
    imported: list[dict[str, Any]] = []
    already_installed: list[dict[str, Any]] = []
    missing: list[dict[str, Any]] = []
    for dep in dependencies.get("tyreBrands", []) if isinstance(dependencies.get("tyreBrands"), list) else []:
        if not isinstance(dep, dict):
            continue
        brand_id = str(dep.get("id") or "").strip()
        if brand_id and _custom_tyre_brand_manifest_by_id(brand_id):
            already_installed.append({"kind": "tyreBrand", "id": brand_id})
            continue
        package_rel = str(dep.get("packagePath") or "").strip()
        if not package_rel:
            missing.append({"kind": "tyreBrand", "id": brand_id, "reason": "dependency package path missing"})
            continue
        package_path = _skin_package_member_path(package_root, package_rel, "tyre brand dependency")
        if not package_path.exists():
            missing.append({"kind": "tyreBrand", "id": brand_id, "path": package_rel, "reason": "dependency package missing"})
            continue
        result = import_custom_tyre_brand_package(package_path, conflict_mode="rename")
        imported.append({"kind": "tyreBrand", "id": result.get("brandId") or brand_id, "sourceId": result.get("sourceBrandId") or brand_id})
    for dep in dependencies.get("driverProfiles", []) if isinstance(dependencies.get("driverProfiles"), list) else []:
        if not isinstance(dep, dict):
            continue
        result = _import_driver_profile_package(package_root, dep)
        status = str(result.get("status") or "")
        if status == "imported":
            imported.append(result)
        elif status == "already_installed":
            already_installed.append(result)
        else:
            missing.append(result)
    return {
        "imported": _json_ready(imported),
        "importedCount": len(imported),
        "alreadyInstalled": _json_ready(already_installed),
        "alreadyInstalledCount": len(already_installed),
        "missing": _json_ready(missing),
        "missingCount": len(missing),
    }

# -----------------------------------------------------------------------------
# TextureMips / DDS conversion
# -----------------------------------------------------------------------------

DXGI_FORMATS = {
    "bc1": (71, 8),
    "bc1_srgb": (72, 8),
    "bc2": (74, 16),
    "bc2_srgb": (75, 16),
    "bc3": (77, 16),
    "bc3_srgb": (78, 16),
    "bc4": (80, 8),
    "bc4_snorm": (81, 8),
    "bc5": (83, 16),
    "bc5_snorm": (84, 16),
    "bc6h_uf16": (95, 16),
    "bc6h_sf16": (96, 16),
    "bc7": (98, 16),
    "bc7_srgb": (99, 16),
}
FORMAT_CHOICES = ["auto"] + sorted(DXGI_FORMATS.keys())
DXGI_TO_BLOCK_BYTES = {71: 8, 72: 8, 74: 16, 75: 16, 77: 16, 78: 16, 80: 8, 81: 8, 83: 16, 84: 16, 95: 16, 96: 16, 98: 16, 99: 16}
DXGI_TO_NAME = {v[0]: k for k, v in DXGI_FORMATS.items()}
FOURCC_TO_DXGI = {b"DXT1": 71, b"DXT3": 74, b"DXT5": 77, b"ATI1": 80, b"BC4U": 80, b"ATI2": 83, b"BC5U": 83}


def read_packed_varints(raw: bytes) -> list[int]:
    values = []
    pos = 0
    while pos < len(raw):
        v, pos = read_varint(raw, pos)
        values.append(v)
    return values


def parse_texture_descriptor(texture_path: str | Path) -> dict[str, Any]:
    data = Path(texture_path).read_bytes()
    fields = parse_fields(data)
    scalars: dict[int, int] = {}
    len_fields: dict[int, list[bytes]] = {}
    for f in fields:
        if f.wire == 0:
            scalars[f.number] = int(f.value)
        elif f.wire == 2 and isinstance(f.value, (bytes, bytearray)):
            len_fields.setdefault(f.number, []).append(bytes(f.value))
    width = scalars.get(1)
    height = scalars.get(2)
    mips = scalars.get(3)
    fmt_code = scalars.get(4)
    if not width or not height or not mips:
        raise RuntimeError("Could not read width/height/mip count from .texture")
    tile_layout_raw = len_fields.get(12, [None])[0]
    if not tile_layout_raw:
        raise RuntimeError("No tiled mip layout field found in .texture")
    layout_fields = parse_fields(tile_layout_raw)
    layout_scalars: dict[int, int] = {}
    layout_len: dict[int, bytes] = {}
    for f in layout_fields:
        if f.wire == 0:
            layout_scalars[f.number] = int(f.value)
        elif f.wire == 2:
            layout_len[f.number] = bytes(f.value)
    tile_w = layout_scalars.get(1, 256)
    tile_h = layout_scalars.get(2, 256)
    mip_tile_offsets = read_packed_varints(layout_len[4]) if 4 in layout_len else None
    mip_tile_counts = read_packed_varints(layout_len[5]) if 5 in layout_len else None
    if not mip_tile_offsets or not mip_tile_counts:
        raise RuntimeError("Could not read tile offsets/counts from .texture")
    return {
        "width": width,
        "height": height,
        "mips": mips,
        "ace_format_code": fmt_code,
        "tile_w": tile_w,
        "tile_h": tile_h,
        "mip_tile_offsets": mip_tile_offsets,
        "mip_tile_counts": mip_tile_counts,
    }


def infer_block_bytes_from_tiled_size(mips_size: int, desc: dict[str, Any]) -> int | None:
    blocks_per_tile = max(1, (int(desc["tile_w"]) + 3) // 4) * max(1, (int(desc["tile_h"]) + 3) // 4)
    denom = sum(desc["mip_tile_counts"]) * blocks_per_tile
    if denom <= 0 or mips_size % denom != 0:
        return None
    return mips_size // denom


EXT_SKIN_BC4_MATERIAL_MAP_STEMS = {"ext_skin_m", "ext_skin_r", "ext_skin_cc", "ext_skin_cr"}
EXT_SKIN_BC4_MATERIAL_MAP_SLOTS = {
    "base_metalnessmap",
    "base_roughnessmap",
    "base_clearcoatmap",
    "base_clearcoatroughnessmap",
}


def is_ext_skin_bc4_material_map(texture_path: str | Path) -> bool:
    return Path(texture_path).stem.lower() in EXT_SKIN_BC4_MATERIAL_MAP_STEMS


def is_ext_skin_bc4_material_slot(slot_name: str | None) -> bool:
    return str(slot_name or "").strip().lower() in EXT_SKIN_BC4_MATERIAL_MAP_SLOTS


def choose_auto_format(texture_path: str | Path, desc: dict[str, Any], mips_size: int, slot_name: str | None = None) -> tuple[str, str]:
    block_bytes = infer_block_bytes_from_tiled_size(mips_size, desc)
    if block_bytes not in (8, 16):
        fmt_code = int(desc.get("ace_format_code") or 0)
        if fmt_code in DXGI_TO_BLOCK_BYTES:
            block_bytes = DXGI_TO_BLOCK_BYTES[fmt_code]
        elif str(slot_name or "").strip().lower() == "spatmap":
            block_bytes = 8
        else:
            texture_name = Path(texture_path).name
            slot_text = f" ({slot_name})" if str(slot_name or "").strip() else ""
            raise RuntimeError(f"Could not infer BC block size for {texture_name}{slot_text} from .texturemips size {mips_size:,}")
    name = Path(texture_path).stem.lower()
    normalized_path = norm_slashes(str(texture_path)).lower()
    normal_tokens = ["normal", "_n", "-n", "_nm", "nm_", "normalmap"]
    color_tokens = ["skin", "base", "basecolor", "diffuse", "albedo", "color", "colour", "_c", "-c", "banner"]
    mask_tokens = ["rough", "metal", "ao", "mask", "opacity", "alpha", "spec", "gloss", "height"]
    if "\\generated\\thumbnails\\" in normalized_path or "/generated/thumbnails/" in normalized_path:
        if block_bytes == 16:
            return "bc7", "auto: generated thumbnail detected; 16 bytes/block -> bc7 selected"
        return "bc1_srgb", "auto: generated thumbnail detected; 8 bytes/block -> bc1_srgb selected"
    if "\\numberplates\\backgrounds\\" in normalized_path or "/numberplates/backgrounds/" in normalized_path:
        if block_bytes == 8:
            return "bc1_srgb", "auto: numberplate background detected; 8 bytes/block -> bc1_srgb selected"
        return "bc7_srgb", "auto: numberplate background detected; 16 bytes/block -> bc7_srgb selected"
    if block_bytes == 8:
        if is_ext_skin_bc4_material_slot(slot_name):
            return "bc4", f"auto: {slot_name} material slot detected; 8 bytes/block -> bc4 selected"
        if is_ext_skin_bc4_material_map(texture_path):
            return "bc4", "auto: EXT_SKIN material map detected; 8 bytes/block -> bc4 selected"
        if str(slot_name or "").strip().lower() == "spatmap":
            return "bc1_srgb", "auto: SpatMap slot detected; 8 bytes/block -> bc1_srgb selected"
        if any(t in name for t in mask_tokens):
            return "bc4", "auto: 8 bytes/block erkannt; bc4 gewÃ¤hlt, weil der Name nach Maske/Grayscale aussieht"
        return "bc1_srgb", "auto: 8 bytes/block erkannt; bc1_srgb gewÃ¤hlt"
    if any(t in name for t in normal_tokens):
        return "bc5", "auto: 16 bytes/block erkannt; bc5 gewÃ¤hlt, weil der Name nach Normal Map aussieht"
    if any(t in name for t in mask_tokens):
        return "bc7", "auto: 16 bytes/block erkannt; bc7 gewÃ¤hlt, weil der Name nach linearen Maskendaten aussieht"
    if any(t in name for t in color_tokens):
        return "bc7_srgb", "auto: 16 bytes/block erkannt; bc7_srgb gewÃ¤hlt"
    return "bc7_srgb", "auto: 16 bytes/block erkannt; bc7_srgb als Default gewÃ¤hlt"


def choose_supported_preview_dds_pixel_format(texture_path: str | Path) -> tuple[str | None, str]:
    texture_path = Path(texture_path)
    mips_path = texture_path.with_suffix(".texturemips")
    desc = parse_texture_descriptor(texture_path)
    format_name, _auto_note = choose_auto_format(texture_path, desc, mips_path.stat().st_size)
    mapping = {
        "bc1_srgb": "DXT1",
    }
    return mapping.get(format_name), format_name


TEXCONV_DDS_FORMATS = {
    "bc1_srgb": "BC1_UNORM_SRGB",
    "bc4": "BC4_UNORM",
    "bc5": "BC5_UNORM",
    "bc7": "BC7_UNORM",
    "bc7_srgb": "BC7_UNORM_SRGB",
}


def validate_project_png_size_for_texture(image_path: str | Path, desc: dict[str, Any], label: str = "Project PNG") -> tuple[int, int]:
    image_path = Path(image_path)
    expected_width = int(desc.get("width") or 0)
    expected_height = int(desc.get("height") or 0)
    if expected_width <= 0 or expected_height <= 0:
        return (0, 0)
    try:
        from PIL import Image
        with Image.open(image_path) as image:
            actual_width, actual_height = image.size
    except Exception as exc:
        raise RuntimeError(f"{label} could not be read before game activation: {exc}") from exc
    if (actual_width, actual_height) != (expected_width, expected_height):
        raise RuntimeError(
            f"{label} has the wrong size: expected {expected_width}x{expected_height} pixels, "
            f"selected project PNG is {actual_width}x{actual_height} pixels.\n\n"
            f"Please export {image_path.name} at {expected_width}x{expected_height} from your image editor, "
            "then try Activate in Game / Update in Game again.\n\n"
            "LiveryLab does not auto-scale livery textures during game activation to avoid blurry or unintended in-game results."
        )
    return actual_width, actual_height


def convert_image_to_dds_with_texconv(
    image_path: str | Path,
    out_dds: str | Path,
    format_name: str,
    texconv_path: str | Path | None = None,
    mip_count: int = 1,
    target_size: tuple[int, int] | None = None,
) -> Path:
    texconv = find_texconv_executable(texconv_path)
    if texconv is None:
        raise RuntimeError(
            "texconv.exe was not found. Please configure the texconv path in LiveryLab before using custom preview PNG thumbnails."
        )
    texconv_format = TEXCONV_DDS_FORMATS.get(format_name)
    if not texconv_format:
        raise RuntimeError(f"Unsupported texconv DDS target format: {format_name}")
    image_path = Path(image_path)
    out_dds = Path(out_dds)
    out_dds.parent.mkdir(parents=True, exist_ok=True)
    temp_dir = out_dds.parent
    generated = temp_dir / f"{image_path.stem}.dds"
    try:
        command = [
            str(texconv),
            "-nologo",
            "-y",
            "-ft",
            "dds",
            "-f",
            texconv_format,
            "-m",
            str(max(1, int(mip_count or 1))),
            "-o",
            str(temp_dir),
            str(image_path),
        ]
        if target_size:
            width, height = target_size
            command[1:1] = ["-w", str(max(1, int(width))), "-h", str(max(1, int(height)))]
        if texconv_format.endswith("_SRGB"):
            command.insert(1, "-srgb")
        process = run_cli_hidden(
            command,
            capture_output=True,
            text=True,
            diagnostic_operation="texconv_image_to_dds",
            diagnostic_context={"input": image_path, "output": out_dds, "format": texconv_format},
        )
        if process.returncode != 0:
            details = "\n".join(part.strip() for part in [process.stdout, process.stderr] if str(part or "").strip())
            raise RuntimeError(f"texconv failed for {image_path.name} -> {texconv_format}\n{details}".strip())
        if not generated.exists():
            raise RuntimeError(f"texconv did not produce a DDS output in {temp_dir}")
        if generated.resolve() == out_dds.resolve():
            return out_dds
        shutil.copyfile(generated, out_dds)
        generated.unlink(missing_ok=True)
    finally:
        pass
    return out_dds


def write_preview_image_as_matching_dds(preview_image: str | Path, out_dds: str | Path, template_texture: str | Path) -> tuple[str, str]:
    pillow_pixel_format, format_name = choose_supported_preview_dds_pixel_format(template_texture)
    texconv = find_texconv_executable()
    if texconv is not None:
        convert_image_to_dds_with_texconv(preview_image, out_dds, format_name, texconv)
        return format_name, f"texconv ({Path(texconv).name})"
    if pillow_pixel_format:
        try:
            from PIL import Image
        except Exception as exc:
            raise RuntimeError(f"Pillow is required for preview thumbnail generation: {exc}") from exc
        with Image.open(preview_image) as image:
            rgba = image.convert("RGBA")
            rgba.save(out_dds, pixel_format=pillow_pixel_format)
        return format_name, f"Pillow {pillow_pixel_format}"
    raise RuntimeError(
        "Custom preview PNG cannot be converted for this thumbnail slot yet. "
        f"The template uses {format_name}, which requires texconv.exe. "
        "Please configure the texconv path in LiveryLab and try again."
    )


def create_dds_header(width: int, height: int, mips: int, dxgi_format: int, block_bytes: int) -> bytes:
    DDSD_CAPS = 0x1; DDSD_HEIGHT = 0x2; DDSD_WIDTH = 0x4; DDSD_PIXELFORMAT = 0x1000; DDSD_MIPMAPCOUNT = 0x20000; DDSD_LINEARSIZE = 0x80000
    DDSCAPS_COMPLEX = 0x8; DDSCAPS_TEXTURE = 0x1000; DDSCAPS_MIPMAP = 0x400000; DDPF_FOURCC = 0x4
    linear_size = max(1, (width + 3) // 4) * max(1, (height + 3) // 4) * block_bytes
    flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_LINEARSIZE
    caps = DDSCAPS_TEXTURE
    if mips > 1:
        flags |= DDSD_MIPMAPCOUNT
        caps |= DDSCAPS_COMPLEX | DDSCAPS_MIPMAP
    ddspf = struct.pack("<II4sIIIII", 32, DDPF_FOURCC, b"DX10", 0, 0, 0, 0, 0)
    header = struct.pack("<IIIIIII11I", 124, flags, height, width, linear_size, 0, mips, *([0] * 11))
    header += ddspf
    header += struct.pack("<IIIII", caps, 0, 0, 0, 0)
    dx10_header = struct.pack("<IIIII", dxgi_format, 3, 0, 1, 0)
    return b"DDS " + header + dx10_header


def parse_dds(dds_path: str | Path) -> dict[str, Any]:
    data = Path(dds_path).read_bytes()
    if data[:4] != b"DDS ":
        raise RuntimeError("Not a DDS file")
    height = struct.unpack_from("<I", data, 12)[0]
    width = struct.unpack_from("<I", data, 16)[0]
    mips = struct.unpack_from("<I", data, 28)[0] or 1
    fourcc = data[84:88]
    if fourcc == b"DX10":
        dxgi_format = struct.unpack_from("<I", data, 128)[0]
        payload_offset = 148
    else:
        if fourcc not in FOURCC_TO_DXGI:
            raise RuntimeError(f"Unsupported old DDS FourCC: {fourcc!r}")
        dxgi_format = FOURCC_TO_DXGI[fourcc]
        payload_offset = 128
    if dxgi_format not in DXGI_TO_BLOCK_BYTES:
        raise RuntimeError(f"Unsupported DXGI format: {dxgi_format}")
    return {"data": data, "width": width, "height": height, "mips": mips, "dxgi_format": dxgi_format, "block_bytes": DXGI_TO_BLOCK_BYTES[dxgi_format], "payload_offset": payload_offset, "payload": data[payload_offset:]}


def detile_to_linear(mips_data: bytes, desc: dict[str, Any], block_bytes: int) -> bytes:
    width = int(desc["width"]); height = int(desc["height"]); mips = int(desc["mips"])
    tile_w = int(desc["tile_w"]); tile_h = int(desc["tile_h"])
    tile_bw = max(1, tile_w // 4); tile_bh = max(1, tile_h // 4)
    tile_row_bytes = tile_bw * block_bytes; tile_size = tile_row_bytes * tile_bh
    expected = sum(desc["mip_tile_counts"]) * tile_size
    if len(mips_data) != expected:
        inferred = infer_block_bytes_from_tiled_size(len(mips_data), desc)
        hint = ""
        if inferred == 8 and block_bytes == 16:
            hint = " Wahrscheinlich 8-byte BC Format: bc1_srgb oder bc4 testen."
        elif inferred == 16 and block_bytes == 8:
            hint = " Wahrscheinlich 16-byte BC Format: bc7_srgb/bc5 testen."
        raise RuntimeError(f".texturemips size mismatch: got {len(mips_data):,}, expected {expected:,}.{hint}")
    out = bytearray()
    cur_w, cur_h = width, height
    for mip in range(mips):
        dst_bw = max(1, (cur_w + 3) // 4); dst_bh = max(1, (cur_h + 3) // 4)
        dst_row_bytes = dst_bw * block_bytes
        dst = bytearray(dst_row_bytes * dst_bh)
        tiles_x = (cur_w + tile_w - 1) // tile_w; tiles_y = (cur_h + tile_h - 1) // tile_h
        first_tile = desc["mip_tile_offsets"][mip]
        for ty in range(tiles_y):
            for tx in range(tiles_x):
                tile_index = first_tile + ty * tiles_x + tx
                src_base = tile_index * tile_size
                dst_x_blocks = tx * tile_bw; dst_y_blocks = ty * tile_bh
                copy_rows = min(tile_bh, dst_bh - dst_y_blocks); copy_cols = min(tile_bw, dst_bw - dst_x_blocks)
                copy_bytes = copy_cols * block_bytes
                for row in range(copy_rows):
                    src_off = src_base + row * tile_row_bytes
                    dst_off = (dst_y_blocks + row) * dst_row_bytes + dst_x_blocks * block_bytes
                    dst[dst_off:dst_off + copy_bytes] = mips_data[src_off:src_off + copy_bytes]
        out.extend(dst)
        cur_w = max(1, cur_w // 2); cur_h = max(1, cur_h // 2)
    return bytes(out)


def tile_from_linear(linear_payload: bytes, desc: dict[str, Any], block_bytes: int) -> bytes:
    width = int(desc["width"]); height = int(desc["height"]); mips = int(desc["mips"])
    tile_w = int(desc["tile_w"]); tile_h = int(desc["tile_h"])
    tile_bw = max(1, tile_w // 4); tile_bh = max(1, tile_h // 4)
    tile_row_bytes = tile_bw * block_bytes; tile_size = tile_row_bytes * tile_bh
    out = bytearray(sum(desc["mip_tile_counts"]) * tile_size)
    src_pos = 0
    cur_w, cur_h = width, height
    for mip in range(mips):
        dst_bw = max(1, (cur_w + 3) // 4); dst_bh = max(1, (cur_h + 3) // 4)
        dst_row_bytes = dst_bw * block_bytes
        mip_size = dst_row_bytes * dst_bh
        src_mip = linear_payload[src_pos:src_pos + mip_size]
        if len(src_mip) < mip_size:
            raise RuntimeError("DDS payload too small for expected mip chain")
        src_pos += mip_size
        tiles_x = (cur_w + tile_w - 1) // tile_w; tiles_y = (cur_h + tile_h - 1) // tile_h
        first_tile = desc["mip_tile_offsets"][mip]
        for ty in range(tiles_y):
            for tx in range(tiles_x):
                tile_index = first_tile + ty * tiles_x + tx
                dst_base = tile_index * tile_size
                src_x_blocks = tx * tile_bw; src_y_blocks = ty * tile_bh
                copy_rows = min(tile_bh, dst_bh - src_y_blocks); copy_cols = min(tile_bw, dst_bw - src_x_blocks)
                copy_bytes = copy_cols * block_bytes
                for row in range(copy_rows):
                    src_off = (src_y_blocks + row) * dst_row_bytes + src_x_blocks * block_bytes
                    dst_off = dst_base + row * tile_row_bytes
                    out[dst_off:dst_off + copy_bytes] = src_mip[src_off:src_off + copy_bytes]
        cur_w = max(1, cur_w // 2); cur_h = max(1, cur_h // 2)
    return bytes(out)


def convert_to_dds(texture_path: str | Path, mips_path: str | Path | None, out_path: str | Path, format_name: str = "auto") -> str:
    texture_path = Path(texture_path)
    mips_path = Path(mips_path) if mips_path else texture_path.with_suffix(".texturemips")
    desc = parse_texture_descriptor(texture_path)
    tiled = mips_path.read_bytes()
    auto_note = ""
    if format_name == "auto":
        format_name, auto_note = choose_auto_format(texture_path, desc, len(tiled))
    dxgi_format, block_bytes = DXGI_FORMATS[format_name]
    linear = detile_to_linear(tiled, desc, block_bytes)
    header = create_dds_header(int(desc["width"]), int(desc["height"]), int(desc["mips"]), dxgi_format, block_bytes)
    backup = safe_write_bytes(Path(out_path), header + linear)
    return f"DDS geschrieben: {out_path}\nFormat: {format_name} / DXGI {dxgi_format}\n{auto_note}\nBackup: {backup or '-'}"


def convert_dds_to_png(dds_path: str | Path, png_path: str | Path | None = None, texconv_path: str | Path | None = None) -> str:
    dds = Path(dds_path)
    out = Path(png_path) if png_path else dds.with_suffix(".png")
    if not dds.exists():
        raise FileNotFoundError(f"DDS file not found: {dds}")
    texconv = find_texconv_executable(texconv_path)
    if texconv is None:
        raise RuntimeError("texconv.exe was not found. Place it in LiveryLab/tools or configure it in Settings.")
    out.parent.mkdir(parents=True, exist_ok=True)
    tmp_dir = create_short_work_temp_dir("dp")
    try:
        process = run_cli_hidden(
            [str(texconv), "-nologo", "-y", "-f", "R8G8B8A8_UNORM", "-ft", "png", "-o", str(tmp_dir), str(dds)],
            capture_output=True,
            text=True,
            diagnostic_operation="texconv_dds_to_png",
            diagnostic_context={"input": dds, "output": out, "format": "R8G8B8A8_UNORM"},
        )
        if process.returncode != 0:
            details = "\n".join(part.strip() for part in [process.stdout, process.stderr] if str(part or "").strip())
            raise RuntimeError(f"texconv failed while exporting PNG for {dds.name}\n{details}".strip())
        generated = tmp_dir / f"{dds.stem}.png"
        if not generated.exists():
            matches = sorted(tmp_dir.glob("*.png"))
            generated = matches[0] if matches else generated
        if not generated.exists():
            raise RuntimeError(f"texconv did not produce a PNG output for {dds.name}")
        backup = backup_file(out) if out.exists() else None
        shutil.copyfile(generated, out)
        return f"PNG written: {out}\nBackup: {backup or '-'}"
    finally:
        cleanup_short_work_temp_dir(tmp_dir)


def texturemips_bytes_from_dds(texture_path: str | Path, dds_path: str | Path) -> bytes:
    desc = parse_texture_descriptor(texture_path)
    dds = parse_dds(dds_path)
    if int(dds["width"]) != int(desc["width"]) or int(dds["height"]) != int(desc["height"]):
        raise RuntimeError(f"DDS dimensions {dds['width']}x{dds['height']} do not match .texture {desc['width']}x{desc['height']}")
    if int(dds["mips"]) < int(desc["mips"]):
        raise RuntimeError(f"DDS has {dds['mips']} mips, .texture expects {desc['mips']}")
    return tile_from_linear(dds["payload"], desc, int(dds["block_bytes"]))


def convert_to_texturemips(texture_path: str | Path, dds_path: str | Path | None, out_path: str | Path, png_path: str | Path | None = None) -> str:
    texture = Path(texture_path)
    out = Path(out_path)
    png = Path(str(png_path).strip()) if str(png_path or "").strip() else None
    dds = Path(str(dds_path).strip()) if str(dds_path or "").strip() else None
    tmp_dir: Path | None = None
    try:
        if png is not None:
            if not png.exists():
                raise FileNotFoundError(f"PNG file not found: {png}")
            desc = parse_texture_descriptor(texture)
            source_mips = texture.with_suffix(".texturemips")
            if not source_mips.exists():
                raise FileNotFoundError(f"Source .texturemips not found beside .texture for format detection: {source_mips}")
            format_name, auto_note = choose_auto_format(texture, desc, source_mips.stat().st_size)
            tmp_dir = create_short_work_temp_dir("tm")
            dds = tmp_dir / "i.dds"
            convert_image_to_dds_with_texconv(png, dds, format_name, None, int(desc["mips"]), (int(desc["width"]), int(desc["height"])))
        if dds is None:
            raise ValueError("DDS or PNG source is required")
        tiled = texturemips_bytes_from_dds(texture, dds)
        backup = safe_write_bytes(out, tiled)
        lines = [f".texturemips geschrieben: {out}", f"Bytes: {len(tiled):,}", f"Backup: {backup or '-'}"]
        if png is not None:
            lines.insert(1, f"PNG source: {png}")
            lines.insert(2, f"DDS intermediate: temporary")
            if auto_note:
                lines.insert(3, auto_note)
        return "\n".join(lines)
    finally:
        cleanup_short_work_temp_dir(tmp_dir)


def _write_game_bytes(path: str | Path, data: bytes, writer: GameFileWriter | None = None, label: str = "", backup: bool = True) -> Path | None:
    if writer is None:
        return safe_write_bytes(Path(path), data)
    result = writer.write_bytes(path, data, label=label, backup=backup)
    return Path(result.backup) if result.backup else None


def _copy_game_file(source: str | Path, target: str | Path, writer: GameFileWriter | None = None, label: str = "", backup: bool = True) -> Path | None:
    if writer is None:
        target_path = Path(target)
        backup_path = backup_file(target_path) if backup and target_path.exists() else None
        target_path.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(source, target_path)
        return backup_path
    result = writer.copy_file(source, target, label=label, backup=backup)
    return Path(result.backup) if result.backup else None


def _move_game_file(source: str | Path, target: str | Path, writer: GameFileWriter | None = None, label: str = "", backup: bool = True) -> Path | None:
    if writer is None:
        target_path = Path(target)
        backup_path = backup_file(target_path) if backup and target_path.exists() else None
        target_path.parent.mkdir(parents=True, exist_ok=True)
        shutil.move(str(source), str(target_path))
        return backup_path
    result = writer.move_file(source, target, label=label, backup=backup)
    return Path(result.backup) if result.backup else None


def make_skin_local_texture_source_path(source_path: str, skin_id: str) -> str | None:
    png_name = Path(source_path.replace("\\", "/")).name
    if not png_name:
        return None
    normalized = source_path.replace("/", "\\")
    marker = "\\Textures\\"
    marker_index = normalized.lower().find(marker.lower())
    if marker_index >= 0:
        return normalized[:marker_index] + f"\\Skins\\{skin_id}\\{png_name}"
    return None


def localize_texture_descriptor_source(texture_path: str | Path, skin_id: str, writer: GameFileWriter | None = None) -> tuple[bool, Path | None]:
    path = Path(texture_path)
    data = path.read_bytes()
    replacements: dict[str, str] = {}
    texture_stem = path.stem.lower()
    for _proto_path, text in collect_proto_strings(data):
        if not text.lower().endswith(".png"):
            continue
        if Path(text.replace("\\", "/")).stem.lower() != texture_stem:
            continue
        replacement = make_skin_local_texture_source_path(text, skin_id)
        if replacement and replacement != text:
            replacements[_normalize_exact_text(text)] = replacement
    if not replacements:
        return False, None
    new_data = patch_strings_recursive_exact(data, replacements)
    if new_data == data:
        return False, None
    backup = _write_game_bytes(path, new_data, writer, label="texture descriptor source localization")
    return True, backup

# -----------------------------------------------------------------------------
# Material inspection
# -----------------------------------------------------------------------------

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


def decode_material_value(raw: bytes) -> object:
    if raw == b"":
        return None
    try:
        fields = parse_fields(raw)
    except Exception:
        text = is_printable_utf8(raw)
        return text if text is not None else {"raw_hex": raw.hex(" ")}
    if len(fields) == 1 and fields[0].number == 1 and fields[0].wire == 5:
        return fixed32_to_float(fields[0].value)
    if len(fields) == 1 and fields[0].number == 2 and fields[0].wire == 2:
        text = is_printable_utf8(fields[0].value)
        if text is not None:
            return text
    if len(fields) == 1 and fields[0].wire == 2:
        nested_raw = fields[0].value
        if nested_raw == b"":
            return []
        try:
            nested = parse_fields(nested_raw)
            vals = []
            for nf in nested:
                if nf.wire == 5 and isinstance(nf.value, (bytes, bytearray)) and len(nf.value) == 4:
                    vals.append(fixed32_to_float(nf.value))
            if vals:
                return vals
            if len(nested) == 1 and nested[0].wire == 2:
                text = is_printable_utf8(nested[0].value)
                if text is not None:
                    return text
        except Exception:
            pass
    return [{"field": f.number, "wire": f.wire, "value": is_printable_utf8(f.value) if isinstance(f.value, bytes) and is_printable_utf8(f.value) else (f.value.hex(" ") if isinstance(f.value, bytes) else f.value)} for f in fields]


def material_parse_entry(raw: bytes) -> tuple[str | None, object]:
    fields = parse_fields(raw)
    name = None
    value_raw = None
    value_varint = None
    for f in fields:
        if f.number == 1 and f.wire == 2 and isinstance(f.value, bytes):
            name = is_printable_utf8(f.value)
        elif f.number == 2:
            if f.wire == 2 and isinstance(f.value, bytes):
                value_raw = f.value
            elif f.wire == 0:
                value_varint = int(f.value)
    if value_raw is not None:
        return name, decode_material_value(value_raw)
    if value_varint is not None:
        return name, value_varint
    return name, None


def material_resolve_ref(root: Path, ref: str) -> Path | None:
    clean = ref.replace("\\", os.sep).replace("/", os.sep)
    candidates = [root / clean]
    parts = clean.split(os.sep)
    if parts and parts[0].lower() == "content":
        candidates.append(root / Path(*parts[1:]))
        candidates.append(root.parent / clean)
    for candidate in candidates:
        if candidate.exists():
            return candidate
    basename = Path(clean).name.lower()
    try:
        for p in root.rglob(basename):
            if p.is_file() and p.name.lower() == basename:
                return p
    except Exception:
        pass
    return None


def inspect_material(material_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    path = Path(material_path)
    data = path.read_bytes()
    fields = parse_fields(data)
    result: dict[str, Any] = {"file": str(path), "size_bytes": len(data), "shader": None, "material_path": None, "texture_slots": [], "parameters": []}
    root_path = Path(root) if root else None
    for f in fields:
        if f.number == 1 and f.wire == 2:
            result["shader"] = is_printable_utf8(f.value)
        elif f.number == 20 and f.wire == 2:
            result["material_path"] = is_printable_utf8(f.value)
        elif f.number == 5 and f.wire == 2:
            name, val = material_parse_entry(f.value)
            slot: dict[str, Any] = {"name": name, "value": val, "is_referenced": isinstance(val, str) and ".texture" in val.lower(), "resolved_path": None, "texturemips_path": None, "suggested_format": None, "width": None, "height": None, "mips": None}
            if root_path and isinstance(val, str):
                resolved = material_resolve_ref(root_path, val)
                if resolved:
                    slot["resolved_path"] = str(resolved)
                    mips = resolved.with_suffix(".texturemips")
                    if mips.exists():
                        slot["texturemips_path"] = str(mips)
                        try:
                            desc = parse_texture_descriptor(resolved)
                            slot["width"] = desc["width"]; slot["height"] = desc["height"]; slot["mips"] = desc["mips"]
                            fmt, _ = choose_auto_format(resolved, desc, mips.stat().st_size, name)
                            slot["suggested_format"] = fmt
                        except Exception:
                            pass
            result["texture_slots"].append(slot)
        elif f.number == 4 and f.wire == 2:
            name, val = material_parse_entry(f.value)
            result["parameters"].append({"name": name, "value": val})
    return result


def inspect_material_readable(material_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    result = inspect_material(material_path, root=root)
    path = Path(material_path)
    texture_references = [
        {
            "name": slot.get("name"),
            "path": slot.get("value"),
            "resolved_path": slot.get("resolved_path"),
            "texturemips_path": slot.get("texturemips_path"),
            "suggested_format": slot.get("suggested_format"),
        }
        for slot in result.get("texture_slots", [])
        if isinstance(slot.get("value"), str) and slot.get("value")
    ]
    string_parameters = [
        {"name": param.get("name"), "value": param.get("value")}
        for param in result.get("parameters", [])
        if isinstance(param.get("value"), str) and param.get("value")
    ]
    result.update(
        {
            "start_number": _infer_start_number_from_material_path(str(path)),
            "texture_references": texture_references,
            "string_parameters": string_parameters,
        }
    )
    return result


def _preview_material_channel(slot_name: str | None) -> str | None:
    name = str(slot_name or "").lower()
    checks = [
        ("Base_BaseColorMap", "baseColor"),
        ("txDiffuse", "baseColor"),
        ("SpatMap", "detailSplat"),
        ("Base_NormalMap", "normal"),
        ("NormalMap", "normal"),
        ("Base_RoughnessMap", "roughness"),
        ("txRoughness", "roughness"),
        ("Base_MetalnessMap", "metalness"),
        ("Base_AmbientOcclusionMap", "ao"),
        ("Base_OpacityMap", "alpha"),
        ("Base_ClearCoatMap", "clearcoat"),
        ("Base_ClearCoatRoughnessMap", "clearcoatRoughness"),
        ("Base_ClearCoatNormalMap", "clearcoatNormal"),
        ("Scuff_Map", "scuff"),
        ("Dirt_Map", "dirt"),
        ("Damage_Map", "damage"),
    ]
    for token, channel in checks:
        if token.lower() == name:
            return channel
    if "normal" in name or name.endswith("_nm"):
        return "normal"
    if "roughness" in name:
        return "roughness"
    if "metalness" in name:
        return "metalness"
    if "ambientocclusion" in name or name.endswith("_ao"):
        return "ao"
    if "opacity" in name or name.endswith("_at"):
        return "alpha"
    if "clearcoat" in name:
        return "clearcoat"
    if "basecolor" in name:
        return "baseColor"
    return None


def _preview_texture_slot_from_ref(game_root: Path, slot_name: str, ref: str) -> dict[str, Any]:
    resolved = resolve_content_path(game_root, ref)
    slot: dict[str, Any] = {
        "name": slot_name,
        "value": norm_slashes(ref),
        "previewChannel": _preview_material_channel(slot_name),
        "resolvedPath": str(resolved),
        "texturemipsPath": None,
        "suggestedFormat": None,
        "width": None,
        "height": None,
        "mips": None,
    }
    mips = resolved.with_suffix(".texturemips")
    if resolved.exists() and mips.exists():
        slot["texturemipsPath"] = str(mips)
        try:
            desc = parse_texture_descriptor(resolved)
            slot["width"] = desc["width"]
            slot["height"] = desc["height"]
            slot["mips"] = desc["mips"]
            fmt, _ = choose_auto_format(resolved, desc, mips.stat().st_size, slot_name)
            slot["suggestedFormat"] = fmt
        except Exception:
            pass
    return slot


def _preview_tyre_texture_set_slots(game_root: Path, texture_set_ref: str | None) -> list[dict[str, Any]]:
    ref = norm_slashes(str(texture_set_ref or "").strip())
    if not ref:
        return []
    path = resolve_content_path(game_root, ref)
    if not path.exists():
        return []
    refs = _content_refs_with_suffix(path.read_bytes(), ".texture")
    if not refs:
        return []
    slots: list[dict[str, Any]] = []
    mapping = [
        ("_blur_c.texture", "Base_BaseColorMapBlur"),
        ("_c.texture", "Base_BaseColorMap"),
        ("_blur_nm.texture", "Base_NormalMapBlur"),
        ("_nm.texture", "Base_NormalMap"),
        ("_blur_ao.texture", "Base_AmbientOcclusionMapBlur"),
        ("_ao.texture", "Base_AmbientOcclusionMap"),
    ]
    used: set[str] = set()
    for suffix, slot_name in mapping:
        found = next((row for row in refs if row.lower().endswith(suffix)), None)
        if not found or found.lower() in used:
            continue
        used.add(found.lower())
        slots.append(_preview_texture_slot_from_ref(game_root, slot_name, found))
    return slots


def _inspect_preview3d_mesh_materials(
    mesh_path: Path,
    game_root: Path,
    source: str,
    source_label: str = "",
    skin_material_path: Path | None = None,
    tyre_texture_set: str | None = None,
) -> dict[str, Any]:
    mesh = inspect_mesh_readable(mesh_path, root=game_root)
    materials: list[dict[str, Any]] = []
    warnings: list[str] = []
    seen_blocks: set[tuple[str, str, str]] = set()
    for block in mesh.get("material_blocks", []):
        block_name = str(block.get("name") or "").strip()
        material_ref = str(block.get("material_path") or "").strip()
        material_path = resolve_content_path(game_root, material_ref) if material_ref else None
        material_source = source
        if skin_material_path and block_name.upper().startswith("EXT_SKIN"):
            material_path = skin_material_path
            material_source = "skin"
        key = (material_source.lower(), block_name.lower(), str(material_path or material_ref).lower())
        if key in seen_blocks:
            continue
        seen_blocks.add(key)

        item: dict[str, Any] = {
            "name": block_name,
            "indexCount": int(block.get("index_count") or 0),
            "materialPath": material_ref,
            "resolvedMaterialPath": str(material_path) if material_path else None,
            "source": material_source,
            "sourceLabel": source_label,
            "meshPath": str(mesh_path),
            "meshName": mesh.get("mesh_name"),
            "exists": bool(material_path and material_path.exists()),
            "shader": None,
            "parentMaterialPath": None,
            "textureSlots": [],
            "parameters": [],
        }
        if material_path and material_path.exists():
            try:
                material = inspect_material(material_path, root=game_root)
                item["shader"] = material.get("shader")
                item["parentMaterialPath"] = material.get("material_path")
                slots: list[dict[str, Any]] = []
                for slot in material.get("texture_slots", []):
                    value = slot.get("value")
                    if not isinstance(value, str) or not value:
                        continue
                    slots.append(
                        {
                            "name": slot.get("name"),
                            "value": value,
                            "previewChannel": _preview_material_channel(str(slot.get("name") or "")),
                            "resolvedPath": slot.get("resolved_path"),
                            "texturemipsPath": slot.get("texturemips_path"),
                            "suggestedFormat": slot.get("suggested_format"),
                            "width": slot.get("width"),
                            "height": slot.get("height"),
                            "mips": slot.get("mips"),
                        }
                    )
                params = [
                    {"name": param.get("name"), "value": param.get("value")}
                    for param in material.get("parameters", [])
                    if param.get("value") not in (None, "", [])
                ]
                if source == "tyre" and tyre_texture_set:
                    tyre_slots = _preview_tyre_texture_set_slots(game_root, tyre_texture_set)
                    if tyre_slots:
                        override_names = {str(slot.get("name") or "").lower() for slot in tyre_slots}
                        slots = tyre_slots + [slot for slot in slots if str(slot.get("name") or "").lower() not in override_names]
                        item["tyreTextureSet"] = norm_slashes(str(tyre_texture_set))
                item["textureSlots"] = slots
                item["parameters"] = params
            except Exception as exc:
                item["error"] = str(exc)
        else:
            warnings.append(f"Material not found for {block_name or '-'}: {material_ref or '-'}")
        materials.append(item)
    return {
        "meshPath": str(mesh_path),
        "meshName": mesh.get("mesh_name"),
        "vertexCount": mesh.get("vertex_count"),
        "triangleCount": mesh.get("triangle_count"),
        "materials": materials,
        "warnings": warnings,
    }


def _preview3d_auxiliary_source_mesh(model: dict[str, Any], game_root: Path) -> tuple[Path | None, dict[str, Any]]:
    info = dict(model)
    mesh_text = str(info.get("meshPath") or info.get("sourceRelPath") or info.get("sourceMeshRelPath") or info.get("sourceMeshPath") or "").strip()
    if not mesh_text:
        glb_text = str(info.get("path") or "").strip()
        if glb_text:
            meta_path = Path(glb_text).with_suffix(".meta.json")
            if meta_path.exists():
                try:
                    meta = json.loads(meta_path.read_text(encoding="utf-8"))
                except Exception:
                    meta = {}
                mesh_text = str(meta.get("sourceMeshRelPath") or meta.get("sourceMeshPath") or "").strip()
                info.setdefault("sourceRelPath", meta.get("sourceMeshRelPath"))
                info.setdefault("meshName", meta.get("meshName"))
    if not mesh_text:
        return None, info
    mesh_path = Path(mesh_text)
    if not mesh_path.is_absolute():
        mesh_path = resolve_content_path(game_root, mesh_text)
    return mesh_path, info


def _merge_preview_material_items(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
    merged: list[dict[str, Any]] = []
    index: dict[tuple[str, str, str], dict[str, Any]] = {}
    for item in items:
        source = str(item.get("source") or "")
        name = str(item.get("name") or "")
        path = str(item.get("resolvedMaterialPath") or item.get("materialPath") or "")
        key = (source.lower(), name.lower(), path.lower())
        label = str(item.get("sourceLabel") or "").strip()
        mesh_path = str(item.get("meshPath") or "").strip()
        if key not in index:
            next_item = dict(item)
            next_item["sourceLabels"] = [label] if label else []
            next_item["sourceMeshPaths"] = [mesh_path] if mesh_path else []
            merged.append(next_item)
            index[key] = next_item
            continue
        target = index[key]
        target["indexCount"] = int(target.get("indexCount") or 0) + int(item.get("indexCount") or 0)
        if label and label not in target.setdefault("sourceLabels", []):
            target["sourceLabels"].append(label)
        if mesh_path and mesh_path not in target.setdefault("sourceMeshPaths", []):
            target["sourceMeshPaths"].append(mesh_path)
    return merged


def inspect_preview3d_materials(project: dict[str, Any] | None) -> dict[str, Any]:
    data = project if isinstance(project, dict) else {}
    game_root_text = str(data.get("gameRoot") or data.get("root") or DEFAULT_ROOT or "").strip()
    if not game_root_text:
        raise ValueError("Game root is required for material inspection")
    game_root = normalize_game_root(game_root_text)
    warnings: list[str] = []

    mesh_path_text = str(data.get("exteriorMeshPath") or "").strip()
    car_content_path = normalize_game_rel_path(str(data.get("carContentPath") or ""))
    if not mesh_path_text and car_content_path:
        exterior_probe = preview3d.find_exterior_mesh(game_root, car_content_path)
        mesh_path_text = str(exterior_probe.get("path") or "")
        warnings.extend(str(row) for row in exterior_probe.get("warnings", []) if row)
    if not mesh_path_text:
        raise ValueError("Exterior mesh path is required for material inspection")

    mesh_path = Path(mesh_path_text)
    if not mesh_path.is_absolute():
        mesh_path = game_root / Path(*split_content_path(mesh_path_text))
    if not mesh_path.exists():
        raise FileNotFoundError(f"Exterior mesh not found: {mesh_path}")

    skin_key = str(data.get("skinKey") or "").strip()
    skin_material_path: Path | None = None
    if skin_key:
        try:
            skin_entry, _skins, _index = _get_my_skin_entry(skin_key)
            files = skin_entry.get("files") if isinstance(skin_entry.get("files"), dict) else {}
            skin_material_rel = str(files.get("material") or "").strip()
            if skin_material_rel:
                skin_material_path = resolve_content_path(game_root, skin_material_rel)
        except Exception as exc:
            warnings.append(f"Could not resolve selected skin material: {exc}")

    exterior = _inspect_preview3d_mesh_materials(mesh_path, game_root, "mesh", "exterior", skin_material_path)
    materials: list[dict[str, Any]] = list(exterior.get("materials") or [])
    warnings.extend(str(row) for row in exterior.get("warnings", []) if row)

    wheel_materials_raw: list[dict[str, Any]] = []
    auxiliary_models = data.get("auxiliaryModels") if isinstance(data.get("auxiliaryModels"), list) else []
    for model in auxiliary_models:
        if not isinstance(model, dict):
            continue
        aux_mesh_path, aux_info = _preview3d_auxiliary_source_mesh(model, game_root)
        label = str(aux_info.get("label") or aux_info.get("meshName") or Path(str(aux_info.get("path") or "")).stem or "aux").strip()
        if aux_mesh_path is None:
            warnings.append(f"Auxiliary source mesh not known for {label or '-'}")
            continue
        if not aux_mesh_path.exists():
            warnings.append(f"Auxiliary source mesh not found for {label or '-'}: {aux_mesh_path}")
            continue
        source_kind = "tyre" if label.lower().startswith("tyre") else "rim"
        tyre_texture_set = str(aux_info.get("tyreTextureSet") or "").strip() if source_kind == "tyre" else ""
        try:
            aux_result = _inspect_preview3d_mesh_materials(aux_mesh_path, game_root, source_kind, label, tyre_texture_set=tyre_texture_set)
            wheel_materials_raw.extend(aux_result.get("materials") or [])
            warnings.extend(str(row) for row in aux_result.get("warnings", []) if row)
        except Exception as exc:
            warnings.append(f"Could not inspect {label or 'auxiliary mesh'} materials: {exc}")

    wheel_materials = _merge_preview_material_items(wheel_materials_raw)
    materials.extend(wheel_materials)
    texture_slot_count = sum(len(item.get("textureSlots") or []) for item in materials)

    return {
        "carModel": data.get("carModel") or Path(str(car_content_path)).name,
        "carContentPath": car_content_path,
        "skinKey": skin_key,
        "meshPath": str(mesh_path),
        "meshName": exterior.get("meshName"),
        "vertexCount": exterior.get("vertexCount"),
        "triangleCount": exterior.get("triangleCount"),
        "materials": materials,
        "wheelMaterials": wheel_materials,
        "wheelMaterialCount": len(wheel_materials),
        "wheelTextureSlotCount": sum(len(item.get("textureSlots") or []) for item in wheel_materials),
        "textureSlotCount": texture_slot_count,
        "warnings": warnings,
    }


def warm_livery_preview_wheel_cache(skin_key: str, root: str | Path | None = None, force: bool = False, apply_wheel_calibrations: bool = True) -> dict[str, Any]:
    build = build_livery_preview_model(skin_key, root, force=force, apply_wheel_calibrations=apply_wheel_calibrations)
    project = build.get("project") if isinstance(build.get("project"), dict) else {}
    inspection = inspect_preview3d_materials(project)
    seen: set[str] = set()
    textures: list[str] = []
    for material in inspection.get("wheelMaterials", []):
        if not isinstance(material, dict):
            continue
        for slot in material.get("textureSlots", []):
            if not isinstance(slot, dict):
                continue
            resolved = str(slot.get("resolvedPath") or "").strip()
            if not resolved or not resolved.lower().endswith(".texture"):
                continue
            key = str(Path(resolved)).lower()
            if key in seen:
                continue
            seen.add(key)
            textures.append(resolved)

    warmed: list[dict[str, Any]] = []
    skipped: list[dict[str, Any]] = []
    for texture in textures:
        try:
            result = _image_to_png_data_url(texture, cache_namespace="rims")
            warmed.append(
                {
                    "texture": texture,
                    "cachePath": result.get("cachePath") or "",
                    "cached": bool(result.get("cached")),
                    "width": result.get("width"),
                    "height": result.get("height"),
                }
            )
        except Exception as exc:
            skipped.append({"texture": texture, "reason": str(exc)})

    auxiliary_models = project.get("auxiliaryModels") if isinstance(project.get("auxiliaryModels"), list) else []
    glb_cached = sum(1 for row in auxiliary_models if isinstance(row, dict) and row.get("cached"))
    glb_built = sum(1 for row in auxiliary_models if isinstance(row, dict) and row.get("cached") is False)
    return {
        "skinKey": skin_key,
        "project": project,
        "glb": build.get("glb") if isinstance(build.get("glb"), dict) else {},
        "wheelPreview": build.get("wheelPreview") if isinstance(build.get("wheelPreview"), dict) else {},
        "wheelMaterialCount": inspection.get("wheelMaterialCount", 0),
        "wheelTextureSlotCount": inspection.get("wheelTextureSlotCount", 0),
        "wheelTextureCount": len(textures),
        "warmed": warmed,
        "warmedCount": len(warmed),
        "skipped": skipped,
        "skippedCount": len(skipped),
        "auxiliaryModelCount": len(auxiliary_models),
        "auxiliaryModelsCached": glb_cached,
        "auxiliaryModelsBuilt": glb_built,
        "warnings": list(build.get("warnings") or []) + list(inspection.get("warnings") or []),
    }


def export_preview3d_wheel_textures(project: dict[str, Any] | None, out_dir: str | Path | None = None) -> dict[str, Any]:
    data = project if isinstance(project, dict) else {}
    inspection = inspect_preview3d_materials(data)
    module_root = Path(__file__).resolve().parent
    export_root = Path(out_dir) if out_dir else module_root / "_debug_textures" / "wheels"
    car_name = preview3d.safe_segment(inspection.get("carModel") or "car", "car")
    skin_key = preview3d.safe_segment(str(inspection.get("skinKey") or "skin").replace(":", "_"), "skin")
    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    target_dir = export_root / f"{car_name}_{skin_key}_{stamp}"
    target_dir.mkdir(parents=True, exist_ok=True)

    exported: list[dict[str, Any]] = []
    skipped: list[dict[str, Any]] = []
    for material in inspection.get("wheelMaterials", []):
        if not isinstance(material, dict):
            continue
        source = preview3d.safe_segment(material.get("source") or "wheel", "wheel")
        material_name = preview3d.safe_segment(material.get("name") or "material", "material")
        for slot in material.get("textureSlots", []):
            if not isinstance(slot, dict):
                continue
            resolved = str(slot.get("resolvedPath") or "").strip()
            if not resolved or not resolved.lower().endswith(".texture"):
                continue
            texture_path = Path(resolved)
            slot_name = preview3d.safe_segment(slot.get("name") or "slot", "slot")
            out_path = target_dir / f"{source}__{material_name}__{slot_name}__{texture_path.stem}.png"
            try:
                converted = convert_texture_to_png(texture_path, out_path, overwrite=True, create_dds=False)
                exported.append(
                    {
                        "source": material.get("source"),
                        "material": material.get("name"),
                        "slot": slot.get("name"),
                        "channel": slot.get("previewChannel"),
                        "texture": str(texture_path),
                        "png": str(converted.get("path") or out_path),
                        "width": slot.get("width"),
                        "height": slot.get("height"),
                        "format": slot.get("suggestedFormat"),
                    }
                )
            except Exception as exc:
                skipped.append(
                    {
                        "source": material.get("source"),
                        "material": material.get("name"),
                        "slot": slot.get("name"),
                        "texture": str(texture_path),
                        "reason": str(exc),
                    }
                )
    manifest = {
        "createdAt": datetime.now().isoformat(timespec="seconds"),
        "carModel": inspection.get("carModel"),
        "skinKey": inspection.get("skinKey"),
        "outDir": str(target_dir),
        "exported": exported,
        "skipped": skipped,
        "warnings": inspection.get("warnings") or [],
    }
    (target_dir / "manifest.json").write_text(json.dumps(manifest, indent=2, ensure_ascii=False), encoding="utf-8")
    return {
        "outDir": str(target_dir),
        "exported": len(exported),
        "skipped": len(skipped),
        "files": exported,
        "manifest": str(target_dir / "manifest.json"),
        "warnings": manifest["warnings"],
    }


def material_readable_summary(result: dict[str, Any]) -> str:
    refs = result.get("texture_references", [])
    params = result.get("string_parameters", [])
    lines = [
        f"Material: {Path(str(result.get('file'))).name}",
        f"Size: {int(result.get('size_bytes') or 0):,} bytes",
        f"Shader: {result.get('shader') or '-'}",
        f"Material path: {result.get('material_path') or '-'}",
        f"Start Number: {result.get('start_number') or '-'}",
        f"Texture references: {len(refs)}",
        f"String parameters: {len(params)}",
        "",
        "Texture references:",
    ]
    for ref in refs:
        lines.append(f"  {ref.get('name')}: {ref.get('path')}")
    if params:
        lines.extend(["", "String parameters:"])
        for param in params:
            lines.append(f"  {param.get('name')}: {param.get('value')}")
    return "\n".join(lines)


def _material_edit_entry(raw: bytes, entry_name: str, new_value: str) -> bytes:
    fields = parse_fields(raw)
    name = None
    changed = False
    new_fields: list[ProtoField] = []
    for field in fields:
        if field.number == 1 and field.wire == 2 and isinstance(field.value, bytes):
            name = is_printable_utf8(field.value)
            new_fields.append(field)
            continue
        if field.number == 2 and field.wire == 2 and name == entry_name:
            new_fields.append(ProtoField(field.number, field.wire, new_value.encode("utf-8")))
            changed = True
            continue
        new_fields.append(field)
    return build_fields(new_fields) if changed else raw


def _material_nested_texture_value(value: str) -> bytes:
    return build_fields([ProtoField(2, 2, norm_slashes(value).encode("utf-8"))])


def _material_edit_texture_slot_entry(raw: bytes, entry_name: str, new_value: str) -> bytes:
    fields = parse_fields(raw)
    name = None
    replacement = _material_nested_texture_value(new_value)
    changed = False
    value_seen = False
    new_fields: list[ProtoField] = []
    for field in fields:
        if field.number == 1 and field.wire == 2 and isinstance(field.value, bytes):
            name = is_printable_utf8(field.value)
            new_fields.append(field)
            continue
        if field.number == 2 and name == entry_name:
            if value_seen:
                changed = True
                continue
            value_seen = True
            if field.wire != 2 or field.value != replacement:
                new_fields.append(ProtoField(2, 2, replacement))
                changed = True
            else:
                new_fields.append(field)
            continue
        new_fields.append(field)
    if name == entry_name and not value_seen:
        new_fields.append(ProtoField(2, 2, replacement))
        changed = True
    return build_fields(new_fields) if changed else raw


def _make_material_texture_slot_field(name: str, value: str) -> ProtoField:
    raw = build_fields([
        ProtoField(1, 2, str(name).encode("utf-8")),
        ProtoField(2, 2, _material_nested_texture_value(value)),
    ])
    return ProtoField(5, 2, raw)


def _set_material_texture_slots_nested(data: bytes, slot_replacements: dict[str, str]) -> bytes:
    replacements = {
        str(name): norm_slashes(str(value))
        for name, value in (slot_replacements or {}).items()
        if str(name) and str(value)
    }
    if not replacements:
        return data
    changed = False
    seen: set[str] = set()
    outer_fields: list[ProtoField] = []
    for field in parse_fields(data):
        if field.number == 5 and field.wire == 2 and isinstance(field.value, bytes):
            try:
                entry_name = None
                for entry_field in parse_fields(bytes(field.value)):
                    if entry_field.number == 1 and entry_field.wire == 2 and isinstance(entry_field.value, bytes):
                        entry_name = is_printable_utf8(entry_field.value)
                        break
            except Exception:
                entry_name = None
            if entry_name in replacements:
                seen.add(entry_name)
                updated = _material_edit_texture_slot_entry(bytes(field.value), entry_name, replacements[entry_name])
                if updated != bytes(field.value):
                    outer_fields.append(ProtoField(field.number, field.wire, updated))
                    changed = True
                    continue
        outer_fields.append(field)
    missing = [name for name in replacements if name not in seen]
    for name in missing:
        outer_fields.append(_make_material_texture_slot_field(name, replacements[name]))
        changed = True
    return build_fields(outer_fields) if changed else data


def edit_material_data(data: bytes, shader: str | None = None, material_path: str | None = None, slot_replacements: dict[str, str] | None = None, parameter_replacements: dict[str, str] | None = None) -> bytes:
    slot_replacements = slot_replacements or {}
    parameter_replacements = parameter_replacements or {}
    new_fields: list[ProtoField] = []
    changed = False
    material_path_seen = False
    for field in parse_fields(data):
        if field.number == 1 and field.wire == 2 and shader is not None:
            current = is_printable_utf8(field.value) or ""
            if current != shader:
                new_fields.append(ProtoField(field.number, field.wire, shader.encode("utf-8")))
                changed = True
            else:
                new_fields.append(field)
            continue
        if field.number == 20 and field.wire == 2 and material_path is not None:
            material_path_seen = True
            replacement = norm_slashes(material_path)
            current = is_printable_utf8(field.value) or ""
            if current != replacement:
                new_fields.append(ProtoField(field.number, field.wire, replacement.encode("utf-8")))
                changed = True
            else:
                new_fields.append(field)
            continue
        if field.number == 5 and field.wire == 2 and slot_replacements:
            try:
                name, value = material_parse_entry(bytes(field.value))
            except Exception:
                name, value = None, None
            if name and name in slot_replacements:
                replacement = norm_slashes(slot_replacements[name])
                updated = _material_edit_texture_slot_entry(bytes(field.value), name, replacement)
                if updated != bytes(field.value):
                    new_fields.append(ProtoField(field.number, field.wire, updated))
                    changed = True
                else:
                    new_fields.append(field)
                continue
        if field.number == 4 and field.wire == 2 and parameter_replacements:
            try:
                name, value = material_parse_entry(bytes(field.value))
            except Exception:
                name, value = None, None
            if name and isinstance(value, str) and name in parameter_replacements:
                replacement = parameter_replacements[name]
                updated = _material_edit_entry(bytes(field.value), name, replacement)
                if updated != bytes(field.value):
                    new_fields.append(ProtoField(field.number, field.wire, updated))
                    changed = True
                else:
                    new_fields.append(field)
                continue
        new_fields.append(field)
    if slot_replacements:
        existing_slots = _material_entry_names(build_fields(new_fields), 5)
        missing_slots = {name: value for name, value in slot_replacements.items() if str(name) and str(name) not in existing_slots}
        if missing_slots:
            new_fields.extend(_make_material_texture_slot_field(name, norm_slashes(value)) for name, value in missing_slots.items())
            changed = True
    return build_fields(new_fields) if changed else data


def _material_override_name(raw: bytes) -> str | None:
    try:
        for field in parse_fields(raw):
            if field.number == 1 and field.wire == 2 and isinstance(field.value, bytes):
                return is_printable_utf8(field.value)
    except Exception:
        return None
    return None


def _make_material_override_field(field_number: int, name: str, value: int = 0) -> ProtoField:
    raw = build_fields([
        ProtoField(1, 2, str(name).encode("utf-8")),
        ProtoField(2, 0, int(value)),
    ])
    return ProtoField(field_number, 2, raw)


def _set_material_override_flags(data: bytes, field_number: int, names: set[str], value: int = 1) -> bytes:
    wanted = {str(name) for name in names if str(name)}
    if not wanted:
        return data
    changed = False
    outer_fields: list[ProtoField] = []
    for field in parse_fields(data):
        if field.number == field_number and field.wire == 2 and isinstance(field.value, bytes):
            try:
                original_entry_fields = parse_fields(bytes(field.value))
            except Exception:
                outer_fields.append(field)
                continue
            entry_name = None
            for entry_field in original_entry_fields:
                if entry_field.number == 1 and entry_field.wire == 2 and isinstance(entry_field.value, bytes):
                    entry_name = is_printable_utf8(entry_field.value)
                    break
            entry_fields: list[ProtoField] = []
            flag_seen = False
            entry_changed = False
            for entry_field in original_entry_fields:
                if entry_field.number == 2 and entry_field.wire == 0 and entry_name in wanted:
                    flag_seen = True
                    if int(entry_field.value) != int(value):
                        entry_fields.append(ProtoField(2, 0, int(value)))
                        entry_changed = True
                    else:
                        entry_fields.append(entry_field)
                    continue
                entry_fields.append(entry_field)
            if entry_name in wanted and not flag_seen:
                entry_fields.append(ProtoField(2, 0, int(value)))
                entry_changed = True
            if entry_changed:
                outer_fields.append(ProtoField(field.number, field.wire, build_fields(entry_fields)))
                changed = True
                continue
        outer_fields.append(field)
    return build_fields(outer_fields) if changed else data


def _ensure_material_override_flags(data: bytes, field_number: int, names: list[str] | tuple[str, ...] | set[str], value: int = 1) -> bytes:
    wanted = [str(name) for name in names if str(name)]
    if not wanted:
        return data
    seen = set(_material_override_names(data, field_number))
    missing = [name for name in wanted if name not in seen]
    if missing:
        fields = parse_fields(data)
        fields.extend(_make_material_override_field(field_number, name, value) for name in missing)
        data = build_fields(fields)
    return _set_material_override_flags(data, field_number, set(wanted), value)


def _material_entry_names(data: bytes, field_number: int) -> set[str]:
    names: set[str] = set()
    try:
        for field in parse_fields(data):
            if field.number != field_number or field.wire != 2 or not isinstance(field.value, bytes):
                continue
            try:
                name, _value = material_parse_entry(bytes(field.value))
            except Exception:
                continue
            if name:
                names.add(str(name))
    except Exception:
        pass
    return names


def _material_override_names(data: bytes, field_number: int) -> list[str]:
    names: list[str] = []
    try:
        for field in parse_fields(data):
            if field.number != field_number or field.wire != 2 or not isinstance(field.value, bytes):
                continue
            name = _material_override_name(bytes(field.value))
            if name and name not in names:
                names.append(name)
    except Exception:
        pass
    return names


def find_material_override_template(root: Path) -> tuple[list[str], list[str]]:
    candidates = [
        root / r"content\cars\ks_ford_mustang_gt3\skins\design_1\ext_banner_pres.material",
        root / r"content\cars\ks_bmw_m2_cs_racing\skins\design_1\ext_banner_bmw.material",
        root / r"content\cars\ks_lamborghini_huracan_st_evo2\skins\design_1\ext_banner_presentation.material",
    ]
    for candidate in candidates:
        if not candidate.exists():
            continue
        data = candidate.read_bytes()
        parameter_names = _material_override_names(data, 21)
        texture_names = _material_override_names(data, 22)
        if texture_names:
            return parameter_names, texture_names
    return [], []


def ensure_material_parent_and_texture_overrides(data: bytes, material_path: str, parameter_slot_names: list[str], texture_slot_names: list[str]) -> bytes:
    """Add the parent material link and texture override markers used by skin-local materials."""
    normalized_parent = norm_slashes(material_path)
    wanted_parameter_slots = [str(name) for name in parameter_slot_names if str(name)]
    wanted_texture_slots = [str(name) for name in texture_slot_names if str(name)]
    new_fields: list[ProtoField] = []
    parent_seen = False
    parameter_overrides_seen: set[str] = set()
    texture_overrides_seen: set[str] = set()
    changed = False
    for field in parse_fields(data):
        if field.number == 20 and field.wire == 2:
            parent_seen = True
            current = is_printable_utf8(field.value) if isinstance(field.value, bytes) else None
            if current != normalized_parent:
                new_fields.append(ProtoField(20, 2, normalized_parent.encode("utf-8")))
                changed = True
            else:
                new_fields.append(field)
            continue
        if field.number == 21 and field.wire == 2 and isinstance(field.value, bytes):
            name = _material_override_name(bytes(field.value))
            if name:
                parameter_overrides_seen.add(name)
        if field.number == 22 and field.wire == 2 and isinstance(field.value, bytes):
            name = _material_override_name(bytes(field.value))
            if name:
                texture_overrides_seen.add(name)
        new_fields.append(field)
    if not parent_seen:
        new_fields.append(ProtoField(20, 2, normalized_parent.encode("utf-8")))
        changed = True
    for name in wanted_parameter_slots:
        if name not in parameter_overrides_seen:
            new_fields.append(_make_material_override_field(21, name, 1))
            changed = True
    for name in wanted_texture_slots:
        if name not in texture_overrides_seen:
            new_fields.append(_make_material_override_field(22, name, 1))
            changed = True
    result = build_fields(new_fields) if changed else data
    result = _set_material_override_flags(result, 21, set(wanted_parameter_slots), 1)
    result = _set_material_override_flags(result, 22, set(wanted_texture_slots), 1)
    return result


def update_material_file(material_path: str | Path, shader: str | None = None, material_ref_path: str | None = None, slot_replacements: dict[str, str] | None = None, parameter_replacements: dict[str, str] | None = None) -> tuple[bool, Path | None]:
    path = Path(material_path)
    old_data = path.read_bytes()
    new_data = edit_material_data(old_data, shader=shader, material_path=material_ref_path, slot_replacements=slot_replacements, parameter_replacements=parameter_replacements)
    if new_data == old_data:
        return False, None
    backup = safe_write_bytes(path, new_data)
    return True, backup


def format_value(v: object) -> str:
    if isinstance(v, float):
        return f"{v:.6g}"
    if isinstance(v, list):
        return "[" + ", ".join(format_value(x) for x in v) + "]"
    if isinstance(v, dict):
        return json.dumps(v, ensure_ascii=False)
    return "" if v is None else str(v)


def classify_parameter(name: str, value: object) -> str:
    lower = name.lower()
    if lower.startswith("has") or "_has" in lower:
        return "toggle"
    if "color" in lower or "basecolor" in lower:
        return "color"
    if "rough" in lower:
        return "roughness"
    if "metal" in lower:
        return "metalness"
    if "normal" in lower:
        return "normal"
    if "clearcoat" in lower:
        return "clearcoat"
    if "uv" in lower:
        return "uv"
    if isinstance(value, list):
        return "vector"
    return "scalar" if isinstance(value, (float, int)) else "other"


def material_summary(result: dict[str, Any]) -> str:
    refs = [s for s in result.get("texture_slots", []) if isinstance(s, dict) and s.get("is_referenced")]
    params = [p for p in result.get("parameters", []) if isinstance(p, dict) and p.get("value") not in (None, [], "")]
    return f"Material: {Path(str(result.get('file'))).name}\nShader: {result.get('shader')}\nMaterial path: {result.get('material_path')}\nTexture slots: {len(refs)} referenced / {len(result.get('texture_slots', []))} total\nParameters with values: {len(params)}"


def write_material_reports(result: dict[str, Any], out_dir: Path) -> str:
    out_dir.mkdir(parents=True, exist_ok=True)
    base = Path(str(result.get("file"))).name
    json_path = out_dir / f"{base}.json"
    csv_path = out_dir / f"{base}.csv"
    md_path = out_dir / f"{base}_report.md"
    safe_write_text(json_path, json.dumps(result, indent=2, ensure_ascii=False))
    with csv_path.open("w", encoding="utf-8", newline="") as f:
        writer = csv.writer(f, delimiter=";")
        writer.writerow(["kind", "name", "value", "category", "resolved_path", "texturemips_path", "suggested_format"])
        for slot in result.get("texture_slots", []):
            writer.writerow(["texture_slot", slot.get("name"), slot.get("value"), "texture" if slot.get("is_referenced") else "", slot.get("resolved_path"), slot.get("texturemips_path"), slot.get("suggested_format")])
        for p in result.get("parameters", []):
            writer.writerow(["parameter", p.get("name"), format_value(p.get("value")), classify_parameter(str(p.get("name") or ""), p.get("value")), "", "", ""])
    lines = [f"# Material Report: {base}", "", f"- **Shader:** `{result.get('shader')}`", f"- **Material path:** `{result.get('material_path')}`", "", "## Referenced textures", "", "| Slot | Texture | Local | Mips | Suggested |", "|---|---|---|---|---|"]
    for slot in result.get("texture_slots", []):
        if slot.get("is_referenced"):
            lines.append(f"| `{slot.get('name')}` | `{slot.get('value')}` | `{slot.get('resolved_path') or ''}` | `{slot.get('texturemips_path') or ''}` | `{slot.get('suggested_format') or ''}` |")
    lines.extend(["", "## Parameters", "", "| Name | Value | Category |", "|---|---:|---|"])
    for p in result.get("parameters", []):
        if p.get("value") not in (None, [], ""):
            lines.append(f"| `{p.get('name')}` | `{format_value(p.get('value'))}` | `{classify_parameter(str(p.get('name') or ''), p.get('value'))}` |")
    safe_write_text(md_path, "\n".join(lines))
    return f"Reports gespeichert:\n{md_path}\n{json_path}\n{csv_path}"

# -----------------------------------------------------------------------------
# Mesh converter
# -----------------------------------------------------------------------------

def _mesh_unpack_float_vectors(raw: bytes, width: int) -> list[tuple[float, ...]]:
    if width <= 0 or len(raw) % (width * 4) != 0:
        raise ValueError(f"Invalid mesh float vector block for width {width}")
    values = struct.unpack("<" + "f" * (len(raw) // 4), raw)
    return [tuple(float(values[i + offset]) for offset in range(width)) for i in range(0, len(values), width)]


def _mesh_decode_index_buffer(raw: bytes) -> list[int]:
    indices: list[int] = []
    pos = 0
    while pos < len(raw):
        value, pos = read_varint(raw, pos)
        indices.append(int(value))
    return indices


def _mesh_pack_float_vectors(values: list[tuple[float, ...]], width: int) -> bytes:
    flat: list[float] = []
    for row in values:
        if len(row) != width:
            raise ValueError(f"Invalid vector width: expected {width}, got {len(row)}")
        flat.extend(float(value) for value in row)
    return struct.pack("<" + "f" * len(flat), *flat) if flat else b""


def _mesh_encode_index_buffer(indices: list[int]) -> bytes:
    out = bytearray()
    for index in indices:
        out.extend(encode_varint(int(index)))
    return bytes(out)


def _mesh_parse_header_block(raw: bytes) -> dict[str, Any]:
    info: dict[str, Any] = {"name": None, "material_path": None, "index_count": None}
    for field in parse_fields(raw):
        if field.wire != 2 and field.number != 3:
            continue
        if field.number == 1 and field.wire == 2:
            info["name"] = is_printable_utf8(field.value)
        elif field.number == 3 and field.wire == 0:
            info["index_count"] = int(field.value)
        elif field.number == 4 and field.wire == 2:
            info["material_path"] = is_printable_utf8(field.value)
    return info


def _mesh_parse_submesh_block(raw: bytes) -> dict[str, Any]:
    info: dict[str, Any] = {"name": None, "material": None, "parameters": []}
    for field in parse_fields(raw):
        if field.wire == 2 and field.number == 1:
            info["name"] = is_printable_utf8(field.value)
        elif field.wire == 2 and field.number == 2:
            info["material"] = is_printable_utf8(field.value)
        elif field.wire == 2 and field.number == 3:
            parameters: dict[int, float] = {}
            try:
                nested = parse_fields(bytes(field.value))
            except Exception:
                nested = []
            for nested_field in nested:
                if nested_field.wire == 5 and isinstance(nested_field.value, (bytes, bytearray)) and len(nested_field.value) == 4:
                    parameters[int(nested_field.number)] = float(struct.unpack("<f", bytes(nested_field.value))[0])
            info["parameters"] = [{"field": number, "value": value} for number, value in sorted(parameters.items())]
    return info


def inspect_mesh_readable(mesh_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    path = Path(mesh_path)
    data = path.read_bytes()
    top_fields = parse_fields(data)
    top_map: dict[int, list[ProtoField]] = {}
    for field in top_fields:
        top_map.setdefault(int(field.number), []).append(field)
    main_field = next((field for field in top_fields if field.number == 5 and field.wire == 2), None)
    if main_field is None:
        raise ValueError(f"Unsupported .mesh layout in {path.name}: missing main payload")

    main_fields = parse_fields(bytes(main_field.value))
    main_map: dict[int, list[ProtoField]] = {}
    for field in main_fields:
        main_map.setdefault(int(field.number), []).append(field)

    material_blocks = [_mesh_parse_header_block(bytes(field.value)) for field in main_map.get(4, []) if field.wire == 2]
    header = material_blocks[0] if material_blocks else {}
    positions = _mesh_unpack_float_vectors(bytes(main_map[5][0].value), 3) if main_map.get(5) else []
    normals = _mesh_unpack_float_vectors(bytes(main_map[6][0].value), 3) if main_map.get(6) else []
    uvs = _mesh_unpack_float_vectors(bytes(main_map[7][0].value), 2) if main_map.get(7) else []
    tangents = _mesh_unpack_float_vectors(bytes(main_map[8][0].value), 4) if main_map.get(8) else []
    indices = _mesh_decode_index_buffer(bytes(main_map[11][0].value)) if main_map.get(11) else []
    submeshes = [_mesh_parse_submesh_block(bytes(field.value)) for field in main_map.get(12, []) if field.wire == 2]

    bounds_min = None
    bounds_max = None
    if top_map.get(6) and top_map[6][0].wire == 2:
        try:
            bounds_min = [float(struct.unpack("<f", bytes(field.value))[0]) for field in parse_fields(bytes(top_map[6][0].value)) if field.wire == 5]
        except Exception:
            bounds_min = None
    if top_map.get(7) and top_map[7][0].wire == 2:
        try:
            bounds_max = [float(struct.unpack("<f", bytes(field.value))[0]) for field in parse_fields(bytes(top_map[7][0].value)) if field.wire == 5]
        except Exception:
            bounds_max = None

    material_path = str(header.get("material_path") or "")
    root_path = Path(root) if root else None
    resolved_material = resolve_content_path(root_path, material_path) if root_path and material_path else None

    return {
        "file": str(path),
        "size_bytes": len(data),
        "mesh_name": header.get("name") or path.stem,
        "material_name": Path(material_path.replace("\\", "/")).stem if material_path else (header.get("name") or path.stem),
        "material_path": material_path or None,
        "material_blocks": material_blocks,
        "material_exists": resolved_material.exists() if resolved_material else None,
        "resolved_material_path": str(resolved_material) if resolved_material else None,
        "vertex_count": len(positions),
        "normal_count": len(normals),
        "uv_count": len(uvs),
        "tangent_count": len(tangents),
        "index_count": len(indices),
        "triangle_count": len(indices) // 3,
        "bounds_min": bounds_min,
        "bounds_max": bounds_max,
        "submeshes": submeshes,
        "positions": positions,
        "normals": normals,
        "uvs": uvs,
        "tangents": tangents,
        "indices": indices,
        "header_index_count": header.get("index_count"),
    }


def mesh_readable_summary(result: dict[str, Any]) -> str:
    lines = [
        f"Mesh: {Path(str(result.get('file'))).name}",
        f"Size: {int(result.get('size_bytes') or 0):,} bytes",
        f"Mesh name: {result.get('mesh_name') or '-'}",
        f"Material: {result.get('material_path') or '-'}",
        f"Vertices: {int(result.get('vertex_count') or 0)}",
        f"Normals: {int(result.get('normal_count') or 0)}",
        f"UVs: {int(result.get('uv_count') or 0)}",
        f"Tangents: {int(result.get('tangent_count') or 0)}",
        f"Indices: {int(result.get('index_count') or 0)}",
        f"Triangles: {int(result.get('triangle_count') or 0)}",
        f"Submeshes: {len(result.get('submeshes', []))}",
    ]
    if result.get("bounds_min") and result.get("bounds_max"):
        lines.extend(
            [
                f"Bounds min: {', '.join(f'{value:.6g}' for value in result.get('bounds_min', []))}",
                f"Bounds max: {', '.join(f'{value:.6g}' for value in result.get('bounds_max', []))}",
            ]
        )
    lines.extend(["", "Submeshes:"])
    for submesh in result.get("submeshes", []):
        name = submesh.get("name") or "-"
        material = submesh.get("material") or result.get("material_name") or "-"
        lines.append(f"  - {name} -> {material}")
    return "\n".join(lines)


def export_mesh_to_obj(mesh_path: str | Path, obj_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    result = inspect_mesh_readable(mesh_path, root=root)
    positions = result.get("positions", [])
    indices = result.get("indices", [])
    if not positions or not indices:
        raise ValueError("Mesh export requires positions and triangle indices")

    normals = result.get("normals", [])
    uvs = result.get("uvs", [])
    write_normals = len(normals) == len(positions)
    write_uvs = len(uvs) == len(positions)

    obj_out = Path(obj_path)
    obj_out.parent.mkdir(parents=True, exist_ok=True)
    mtl_out = obj_out.with_suffix(".mtl")
    material_name = str(result.get("material_name") or result.get("mesh_name") or obj_out.stem)
    mesh_name = str(result.get("mesh_name") or obj_out.stem)

    mtl_lines = [
        f"# Exported from {Path(str(result.get('file'))).name}",
        f"# Source material: {result.get('material_path') or '-'}",
        "",
        f"newmtl {material_name}",
        "Ka 1.000000 1.000000 1.000000",
        "Kd 1.000000 1.000000 1.000000",
        "Ks 0.000000 0.000000 0.000000",
        "d 1.000000",
        "illum 2",
        "",
    ]
    safe_write_text(mtl_out, "\n".join(mtl_lines))

    obj_lines = [
        f"# Exported from {Path(str(result.get('file'))).name}",
        f"mtllib {mtl_out.name}",
        f"o {mesh_name}",
        f"g {mesh_name}",
        f"usemtl {material_name}",
        "s off",
    ]
    for x, y, z in positions:
        obj_lines.append(f"v {x:.9g} {y:.9g} {z:.9g}")
    if write_uvs:
        for u, v in uvs:
            obj_lines.append(f"vt {u:.9g} {v:.9g}")
    if write_normals:
        for x, y, z in normals:
            obj_lines.append(f"vn {x:.9g} {y:.9g} {z:.9g}")
    for i in range(0, len(indices), 3):
        a, b, c = (indices[i] + 1, indices[i + 1] + 1, indices[i + 2] + 1)
        if write_uvs and write_normals:
            obj_lines.append(f"f {a}/{a}/{a} {b}/{b}/{b} {c}/{c}/{c}")
        elif write_uvs:
            obj_lines.append(f"f {a}/{a} {b}/{b} {c}/{c}")
        elif write_normals:
            obj_lines.append(f"f {a}//{a} {b}//{b} {c}//{c}")
        else:
            obj_lines.append(f"f {a} {b} {c}")
    safe_write_text(obj_out, "\n".join(obj_lines) + "\n")

    return {
        "obj_path": str(obj_out),
        "mtl_path": str(mtl_out),
        "mesh_name": mesh_name,
        "material_name": material_name,
        "vertex_count": len(positions),
        "triangle_count": len(indices) // 3,
        "submesh_count": len(result.get("submeshes", [])),
        "source_material_path": result.get("material_path"),
    }


def inspect_rimmesh_readable(rimmesh_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    path = Path(rimmesh_path)
    root_path = Path(root) if root else None
    data = path.read_bytes()
    values: dict[int, float] = {}
    strings: dict[int, list[str]] = {}
    for field in parse_fields(data):
        if field.wire == 5 and len(field.value) == 4:
            values[field.number] = struct.unpack("<f", bytes(field.value))[0]
        elif field.wire == 2:
            try:
                text = bytes(field.value).decode("utf-8")
            except UnicodeDecodeError:
                continue
            if all((ch >= " " or ch in "\r\n\t") for ch in text):
                strings.setdefault(field.number, []).append(text)

    def first(number: int) -> str:
        entries = strings.get(number) or []
        return entries[0] if entries else ""

    def exists(rel_path: str) -> bool:
        if not rel_path:
            return False
        p = Path(rel_path)
        if p.is_absolute():
            return p.exists()
        if root_path:
            return (root_path / rel_path.replace("\\", "/")).exists()
        return False

    mesh_refs = {
        "lf": first(5),
        "rf": first(6),
        "blur_lf": first(7),
        "blur_rf": first(8),
    }
    result = {
        "file": str(path),
        "size_bytes": path.stat().st_size if path.exists() else 0,
        "diameter": values.get(2),
        "width": values.get(3),
        "offset": values.get(4),
        "mesh_lf": mesh_refs["lf"],
        "mesh_rf": mesh_refs["rf"],
        "mesh_blur_lf": mesh_refs["blur_lf"],
        "mesh_blur_rf": mesh_refs["blur_rf"],
        "tyre_mesh": first(9),
        "rimdesign": first(10),
        "rimdesign_alt": first(12),
        "name": first(11),
        "brand": first(102),
        "mesh_refs": mesh_refs,
        "missing_refs": [],
    }
    missing: list[str] = []
    for label, rel_path in [
        ("LF mesh", result["mesh_lf"]),
        ("RF mesh", result["mesh_rf"]),
        ("LF blur mesh", result["mesh_blur_lf"]),
        ("RF blur mesh", result["mesh_blur_rf"]),
        ("Tyre mesh", result["tyre_mesh"]),
        ("Rim design", result["rimdesign"]),
    ]:
        if rel_path and not exists(str(rel_path)):
            missing.append(f"{label}: {rel_path}")
    result["missing_refs"] = missing
    return result


def rimmesh_readable_summary(result: dict[str, Any]) -> str:
    lines = [
        f"Rimmesh: {Path(str(result.get('file') or '')).name}",
        f"Name: {result.get('name') or '-'}",
        f"Brand: {result.get('brand') or '-'}",
        f"Diameter: {result.get('diameter') or '-'}",
        f"Width: {result.get('width') or '-'}",
        f"Offset: {result.get('offset') if result.get('offset') is not None else '-'}",
        "",
        "Meshes:",
        f"  LF: {result.get('mesh_lf') or '-'}",
        f"  RF: {result.get('mesh_rf') or '-'}",
        f"  Blur LF: {result.get('mesh_blur_lf') or '-'}",
        f"  Blur RF: {result.get('mesh_blur_rf') or '-'}",
        "",
        f"Tyre mesh: {result.get('tyre_mesh') or '-'}",
        f"Rim design: {result.get('rimdesign') or '-'}",
    ]
    missing = result.get("missing_refs") or []
    if missing:
        lines.extend(["", "Missing references:", *[f"  {row}" for row in missing]])
    return "\n".join(lines)


def create_rimmesh_swap(source_rimmesh: str | Path, target_rimmesh: str | Path, out_rimmesh: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    source = inspect_rimmesh_readable(source_rimmesh, root)
    target = inspect_rimmesh_readable(target_rimmesh, root)
    out = Path(out_rimmesh)
    writer: GameFileWriter | None = None
    root_path: Path | None = None
    if str(root or "").strip():
        root_path = normalize_game_root(root)
        if any(_path_is_within(candidate, root_path) for candidate in _target_candidates_for_game_root(out, root_path)):
            writer = GameFileWriter(root_path, "create rimmesh swap", [out])
    if not source.get("mesh_lf") or not source.get("mesh_rf"):
        raise ValueError("Source rimmesh is missing LF/RF mesh references")
    if not source.get("mesh_blur_lf") or not source.get("mesh_blur_rf"):
        raise ValueError("Source rimmesh is missing LF/RF blur mesh references")
    replacements = {
        str(target.get("mesh_lf") or ""): str(source.get("mesh_lf") or ""),
        str(target.get("mesh_rf") or ""): str(source.get("mesh_rf") or ""),
        str(target.get("mesh_blur_lf") or ""): str(source.get("mesh_blur_lf") or ""),
        str(target.get("mesh_blur_rf") or ""): str(source.get("mesh_blur_rf") or ""),
    }
    replacements = {old: new for old, new in replacements.items() if old and new and old != new}
    if not replacements:
        raise ValueError("Source and target rimmesh already use the same rim mesh references")
    new_data = patch_strings_recursive_exact(Path(target_rimmesh).read_bytes(), replacements)
    if writer:
        write_result = writer.write_bytes(out, new_data, label="rimmesh", backup=True)
    else:
        backup = safe_write_bytes(out, new_data)
        write_result = None
    result = inspect_rimmesh_readable(out, root)
    result.update({
        "source": source,
        "target": target,
        "out": str(out),
        "compatibility": compare_rimmesh_compatibility(source, target),
        "replacements": replacements,
    })
    if write_result:
        result.update({
            "backup": write_result.backup,
            "writeResult": write_result.to_dict(),
            "writer": writer.to_dict() if writer else {},
        })
    else:
        result.update({"backup": str(backup) if backup else ""})
    return result


def _proto_utf8_strings(data: bytes) -> list[str]:
    values: list[str] = []
    try:
        fields = parse_fields(data)
    except Exception:
        return values
    for field in fields:
        if field.wire != 2:
            continue
        raw = bytes(field.value)
        text = is_printable_utf8(raw)
        if text is not None:
            values.append(text)
            continue
        values.extend(_proto_utf8_strings(raw))
    return values


def _compatiblerim_rimmesh_refs(data: bytes) -> list[str]:
    refs: list[str] = []
    for text in _proto_utf8_strings(data):
        normalized = norm_slashes(text)
        if normalized.lower().endswith(".rimmesh") and normalized.lower().startswith("content\\"):
            refs.append(normalized)
    return refs


def _compatiblerim_compatibletyres_refs(data: bytes) -> list[str]:
    refs: list[str] = []
    for text in _proto_utf8_strings(data):
        normalized = norm_slashes(text)
        if normalized.lower().endswith(".compatibletyres") and normalized.lower().startswith("content\\"):
            refs.append(normalized)
    return refs


def _content_refs_with_suffix(data: bytes, suffix: str) -> list[str]:
    clean_suffix = suffix.lower().lstrip(".")
    text = data.decode("utf-8", errors="ignore")
    pattern = re.compile(rf"content[\\/]+cars[\\/]+[^\x00\r\n]+?\.{re.escape(clean_suffix)}", re.IGNORECASE)
    refs = [norm_slashes(match.group(0)) for match in pattern.finditer(text)]
    return list(dict.fromkeys(refs))


def _append_actor_compatible_rim(data: bytes, compat_ref: str) -> bytes:
    compat_ref = norm_slashes(compat_ref)
    if compat_ref.encode("utf-8") in data:
        return data
    top_fields = parse_fields(data)
    out_top: list[ProtoField] = []
    changed = False
    for field in top_fields:
        if field.number == 103 and field.wire == 2:
            fields_103 = parse_fields(bytes(field.value))
            out_103: list[ProtoField] = []
            for subfield in fields_103:
                if subfield.number == 13 and subfield.wire == 2:
                    fields_13 = parse_fields(bytes(subfield.value))
                    fields_13.append(ProtoField(7, 2, compat_ref.encode("utf-8")))
                    out_103.append(ProtoField(subfield.number, subfield.wire, build_fields(fields_13)))
                    changed = True
                else:
                    out_103.append(subfield)
            out_top.append(ProtoField(field.number, field.wire, build_fields(out_103)))
        else:
            out_top.append(field)
    if not changed:
        raise ValueError("Could not find actor compatible rim registry (field 103.13)")
    return build_fields(out_top)


def _append_cardesign_compatible_rim(data: bytes, compat_ref: str) -> bytes:
    compat_ref = norm_slashes(compat_ref)
    if compat_ref.encode("utf-8") in data:
        return data
    fields = parse_fields(data)
    fields.append(ProtoField(2, 2, compat_ref.encode("utf-8")))
    return build_fields(fields)


def _set_rimmesh_display_name(data: bytes, display_name: str) -> bytes:
    name = str(display_name).strip()
    if not name:
        return data
    fields = parse_fields(data)
    out: list[ProtoField] = []
    written = False
    for field in fields:
        if field.number == 11 and field.wire == 2 and is_printable_utf8(bytes(field.value)) is not None:
            out.append(ProtoField(field.number, field.wire, name.encode("utf-8")))
            written = True
        else:
            out.append(field)
    if not written:
        out.append(ProtoField(11, 2, name.encode("utf-8")))
    return build_fields(out)


def _remove_actor_compatible_rim(data: bytes, compat_ref: str) -> tuple[bytes, bool]:
    target = norm_slashes(compat_ref)
    top_fields = parse_fields(data)
    out_top: list[ProtoField] = []
    changed = False
    for field in top_fields:
        if field.number != 103 or field.wire != 2:
            out_top.append(field)
            continue
        fields_103 = parse_fields(bytes(field.value))
        out_103: list[ProtoField] = []
        patched = False
        for subfield in fields_103:
            if subfield.number != 13 or subfield.wire != 2:
                out_103.append(subfield)
                continue
            fields_13 = parse_fields(bytes(subfield.value))
            kept: list[ProtoField] = []
            removed_here = False
            for resource in fields_13:
                if resource.number == 7 and resource.wire == 2:
                    text = is_printable_utf8(bytes(resource.value))
                    if text is not None and norm_slashes(text) == target:
                        changed = True
                        removed_here = True
                        continue
                kept.append(resource)
            out_103.append(ProtoField(subfield.number, subfield.wire, build_fields(kept)) if removed_here else subfield)
            patched = patched or removed_here
        out_top.append(ProtoField(field.number, field.wire, build_fields(out_103)) if patched else field)
    return (build_fields(out_top), changed) if changed else (data, False)


def _remove_cardesign_compatible_rim(data: bytes, compat_ref: str) -> tuple[bytes, bool]:
    target = norm_slashes(compat_ref)
    fields = parse_fields(data)
    kept: list[ProtoField] = []
    changed = False
    for field in fields:
        if field.number == 2 and field.wire == 2:
            text = is_printable_utf8(bytes(field.value))
            if text is not None and norm_slashes(text) == target:
                changed = True
                continue
        kept.append(field)
    return (build_fields(kept), True) if changed else (data, False)


def _custom_rim_marker_path(compatiblerim_path: Path) -> Path:
    return compatiblerim_path.with_suffix(".liverylab-rim.json")


def _ace_profile_savedcars_dirs() -> list[Path]:
    profile_root = saved_games_paths.ace_root() / "ProfileData"
    if not profile_root.exists():
        return []
    dirs: list[Path] = []
    for profile in profile_root.iterdir():
        if not profile.is_dir():
            continue
        for relative in (
            Path("OpenData") / "SavedCars",
            Path("CareerData") / "SavedCars",
        ):
            path = profile / relative
            if path.exists() and path.is_dir():
                dirs.append(path)
    return dirs


def _skip_savedcar_scan_path(path: Path) -> bool:
    return any(part.lower().startswith("backup") or part.lower() == "_acevo_backups" for part in path.parts)


def _build_savedcars_scan_index() -> dict[str, Any]:
    profile_root = saved_games_paths.ace_root() / "ProfileData"
    if not profile_root.exists():
        return {"items": [], "errors": []}
    items: list[dict[str, Any]] = []
    errors: list[str] = []
    seen: set[str] = set()
    try:
        savedcars_dirs = _ace_profile_savedcars_dirs()
    except Exception as exc:
        return {"items": [], "errors": [f"{profile_root}: {exc}"]}
    for savedcars_dir in savedcars_dirs:
        try:
            candidates = list(savedcars_dir.rglob("*.carfinalstate*"))
        except Exception as exc:
            errors.append(f"{savedcars_dir}: {exc}")
            continue
        for savedcar in candidates:
            try:
                if not savedcar.is_file() or _skip_savedcar_scan_path(savedcar):
                    continue
                key = str(savedcar.resolve()).lower()
                if key in seen:
                    continue
                seen.add(key)
                text = savedcar.read_bytes().decode("utf-8", errors="ignore").lower()
            except Exception as exc:
                errors.append(f"{savedcar}: {exc}")
                continue
            items.append({"path": savedcar, "text": text})
    return {"items": items, "errors": errors}


def _scan_savedcars_for_path(path_ref: str, scan_index: dict[str, Any] | None = None) -> tuple[list[Path], list[str]]:
    profile_root = saved_games_paths.ace_root() / "ProfileData"
    if not profile_root.exists():
        return [], []
    target = norm_slashes(path_ref).lower()
    if not target:
        return [], []
    variants = {
        target,
        target.replace("/", "\\"),
    }
    hits: list[Path] = []
    errors: list[str] = []
    seen: set[str] = set()
    if scan_index is not None:
        index_items = scan_index.get("items") if isinstance(scan_index.get("items"), list) else []
        for row in index_items:
            if not isinstance(row, dict):
                continue
            savedcar = row.get("path")
            text = str(row.get("text") or "")
            if not isinstance(savedcar, Path):
                savedcar = Path(str(savedcar or ""))
            if not savedcar or not text:
                continue
            key = str(savedcar).lower()
            if key in seen:
                continue
            if any(variant in text for variant in variants):
                seen.add(key)
                hits.append(savedcar)
        index_errors = scan_index.get("errors") if isinstance(scan_index.get("errors"), list) else []
        return hits, [str(error) for error in index_errors]
    try:
        savedcars_dirs = _ace_profile_savedcars_dirs()
    except Exception as exc:
        return [], [f"{profile_root}: {exc}"]
    for savedcars_dir in savedcars_dirs:
        try:
            candidates = list(savedcars_dir.rglob("*.carfinalstate*"))
        except Exception as exc:
            errors.append(f"{savedcars_dir}: {exc}")
            continue
        for savedcar in candidates:
            try:
                if not savedcar.is_file():
                    continue
                key = str(savedcar.resolve()).lower()
                if key in seen:
                    continue
                text = savedcar.read_bytes().decode("utf-8", errors="ignore").lower()
            except Exception as exc:
                errors.append(f"{savedcar}: {exc}")
                continue
            if any(variant in text for variant in variants):
                seen.add(key)
                hits.append(savedcar)
    return hits, errors


def _savedcars_referencing_path(path_ref: str) -> list[Path]:
    hits, _errors = _scan_savedcars_for_path(path_ref)
    return hits


def _ace_savedcars_thumbnail_root() -> Path:
    return saved_games_paths.ace_root() / "SavedCars" / "Thumbnails"


def _file_mtime_iso(path: Path) -> str:
    try:
        return datetime.fromtimestamp(path.stat().st_mtime).isoformat(timespec="seconds")
    except Exception:
        return ""


def _ace_savedcar_guid_from_path(path: Path) -> str:
    match = re.search(
        r"([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})",
        path.name,
    )
    return match.group(1).lower() if match else ""


def _ace_savedcar_paths_for_guids(guids: Iterable[str]) -> tuple[list[Path], list[str]]:
    targets = {
        guid
        for value in guids
        if (guid := official_saved_car_state.normalize_guid(value))
    }
    if not targets:
        return [], []
    paths: list[Path] = []
    errors: list[str] = []
    seen: set[str] = set()
    try:
        roots = _ace_profile_savedcars_dirs()
    except Exception as exc:
        return [], [str(exc)]
    for root in roots:
        try:
            candidates = root.glob(f"*{official_saved_car_state.SAVED_CAR_SUFFIX}")
            for path in candidates:
                guid = _ace_savedcar_guid_from_path(path)
                key = str(path.resolve()).lower()
                if key in seen or not path.is_file():
                    continue
                matches_target = guid in targets
                if not matches_target:
                    inspection = official_saved_car_state.inspect_saved_car_file(path)
                    matches_target = bool(
                        inspection.get("valid")
                        and any(
                            official_saved_car_state.matches_official_livery_guid(inspection, target)
                            for target in targets
                        )
                    )
                if not matches_target:
                    continue
                seen.add(key)
                paths.append(path)
        except Exception as exc:
            errors.append(f"{root}: {exc}")
    return paths, errors


def _ace_savedcar_thumbnail_descriptor(texture_path: str | Path) -> dict[str, Any]:
    path = Path(texture_path)
    fields = parse_fields(path.read_bytes())
    scalars: dict[int, int] = {}
    has_tiled_layout = False
    for field in fields:
        if field.wire == 0:
            scalars[field.number] = int(field.value)
        elif field.number == 12 and field.wire == 2:
            has_tiled_layout = True
    width = int(scalars.get(1) or 0)
    height = int(scalars.get(2) or 0)
    mips = int(scalars.get(3) or 0)
    fmt_code = int(scalars.get(4) or 0)
    if not width or not height or not mips:
        raise RuntimeError("Could not read ACE saved car thumbnail dimensions")
    return {
        "width": width,
        "height": height,
        "mips": mips,
        "ace_format_code": fmt_code,
        "has_tiled_layout": has_tiled_layout,
    }


def _is_ace_savedcar_raw_thumbnail(texture_path: str | Path, mips_path: str | Path | None = None) -> bool:
    texture = Path(texture_path)
    mips = Path(mips_path) if mips_path else texture.with_suffix(".texturemips")
    if not texture.exists() or not mips.exists():
        return False
    try:
        desc = _ace_savedcar_thumbnail_descriptor(texture)
        if bool(desc.get("has_tiled_layout")):
            return False
        expected_size = int(desc["width"]) * int(desc["height"]) * 4
        return expected_size > 0 and mips.stat().st_size == expected_size
    except Exception:
        return False


def _ace_savedcar_raw_thumbnail_to_png(texture_path: str | Path, out_path: str | Path) -> dict[str, Any]:
    from PIL import Image

    texture = Path(texture_path)
    mips = texture.with_suffix(".texturemips")
    if not _is_ace_savedcar_raw_thumbnail(texture, mips):
        raise RuntimeError(f"Not an ACE saved car raw thumbnail: {texture}")
    desc = _ace_savedcar_thumbnail_descriptor(texture)
    width = int(desc["width"])
    height = int(desc["height"])
    raw = mips.read_bytes()
    image = Image.frombytes("RGBA", (width, height), raw, "raw", "RGBA")
    target = Path(out_path)
    target.parent.mkdir(parents=True, exist_ok=True)
    image.save(target, format="PNG")
    return {
        "path": str(texture),
        "mipsPath": str(mips),
        "pngPath": str(target),
        "width": width,
        "height": height,
        "mime": "image/png",
    }


def _ace_savedcar_raw_thumbnail_cache_path(texture_path: Path, mips_path: Path) -> Path:
    return _texture_preview_cache_path(texture_path, mips_path, "ace_savedcars_rgba", max_dimension=0)


def _ace_savedcar_raw_thumbnail_data_url(texture_path: str | Path) -> dict[str, Any]:
    texture = Path(texture_path)
    mips = texture.with_suffix(".texturemips")
    cache_path = _ace_savedcar_raw_thumbnail_cache_path(texture, mips)
    if cache_path.exists():
        return _read_png_as_data_url(cache_path, texture, cached=True)
    _ace_savedcar_raw_thumbnail_to_png(texture, cache_path)
    return _read_png_as_data_url(cache_path, texture, cached=True)


def _ace_savedcar_thumbnail_path_for_guid(guid: str) -> tuple[Path | None, Path | None]:
    clean = str(guid or "").strip().lower()
    if not clean:
        return None, None
    texture = _ace_savedcars_thumbnail_root() / f"{clean}.texture"
    mips = texture.with_suffix(".texturemips")
    return texture, mips


def _ace_savedcar_thumbnail_status_from_hit(
    savedcar: Path,
    matched_ref: str,
    match_kind: str,
    inspection: dict[str, Any] | None = None,
) -> dict[str, Any]:
    savedcar_inspection = inspection if isinstance(inspection, dict) else official_saved_car_state.inspect_saved_car_file(savedcar)
    thumbnail_guids: list[str] = []
    for value in (
        _ace_savedcar_guid_from_path(savedcar),
        savedcar_inspection.get("liveryLinkerGuid"),
        savedcar_inspection.get("carGuid"),
    ):
        guid = official_saved_car_state.normalize_guid(value)
        if guid and guid not in thumbnail_guids:
            thumbnail_guids.append(guid)

    candidates: list[dict[str, Any]] = []
    for guid in thumbnail_guids:
        texture, mips = _ace_savedcar_thumbnail_path_for_guid(guid)
        available = bool(texture and mips and _is_ace_savedcar_raw_thumbnail(texture, mips))
        existing_paths = [path for path in (texture, mips) if path and path.exists()]
        updated_ns = max((int(_file_signature(path).get("mtime_ns") or 0) for path in existing_paths), default=0)
        candidates.append({
            "guid": guid,
            "texture": texture,
            "mips": mips,
            "available": available,
            "updated_ns": updated_ns,
        })

    available_candidates = [row for row in candidates if row["available"]]
    if available_candidates:
        selected = max(available_candidates, key=lambda row: int(row["updated_ns"]))
    elif candidates:
        selected = max(candidates, key=lambda row: int(row["updated_ns"]))
    else:
        selected = {"guid": "", "texture": None, "mips": None, "available": False}
    guid = str(selected.get("guid") or "")
    texture = selected.get("texture")
    mips = selected.get("mips")
    available = bool(selected.get("available"))
    return {
        "available": available,
        "status": "available" if available else "saved_car_found",
        "message": "AC EVO thumbnail available" if available else "AC EVO saved car found, but its thumbnail is missing",
        "guid": guid,
        "saved_car_path": str(savedcar),
        "saved_car_updated_at": _file_mtime_iso(savedcar),
        "thumbnail_texture_path": str(texture) if isinstance(texture, Path) and texture.exists() else "",
        "thumbnail_mips_path": str(mips) if isinstance(mips, Path) and mips.exists() else "",
        "matched_ref": matched_ref,
        "matched_by": match_kind,
    }


def _ace_savedcar_thumbnail_for_skin_entry(
    entry: dict[str, Any],
    game_root: Path | None = None,
    savedcars_index: dict[str, Any] | None = None,
) -> dict[str, Any]:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    official_details = entry.get("official_external") if isinstance(entry.get("official_external"), dict) else {}
    generated_saved_car = (
        official_details.get("generatedSavedCar")
        if isinstance(official_details.get("generatedSavedCar"), dict)
        else {}
    )
    item_root = game_root
    if item_root is None and str(entry.get("game_root") or "").strip():
        try:
            item_root = normalize_game_root(str(entry.get("game_root") or ""))
        except Exception:
            item_root = None

    direct_guids: list[str] = []
    for value in (
        entry.get("car_guid"),
        official_details.get("carGuid"),
        official_details.get("linkedSavedCarGuid"),
        generated_saved_car.get("carGuid"),
    ):
        guid = official_saved_car_state.normalize_guid(value)
        if guid and guid not in direct_guids:
            direct_guids.append(guid)

    scan_index = savedcars_index if isinstance(savedcars_index, dict) else None
    direct_hits: list[dict[str, Any]] = []
    direct_errors: list[str] = []
    direct_seen: set[str] = set()
    direct_paths: list[Path] = []
    direct_inspections: dict[str, dict[str, Any]] = {}
    if scan_index is not None:
        direct_errors.extend(str(row) for row in (scan_index.get("errors") or []) if str(row).strip())
        for row in scan_index.get("items") or []:
            if not isinstance(row, dict):
                continue
            savedcar = row.get("path")
            savedcar_path = savedcar if isinstance(savedcar, Path) else Path(str(savedcar or ""))
            direct_paths.append(savedcar_path)
            inspection = row.get("inspection")
            if not isinstance(inspection, dict):
                inspection = official_saved_car_state.inspect_saved_car_file(savedcar_path)
                row["inspection"] = inspection
            direct_inspections[str(savedcar_path).lower()] = inspection
    else:
        direct_paths, direct_errors = _ace_savedcar_paths_for_guids(direct_guids)

    for savedcar in direct_paths:
        path_key = str(savedcar).lower()
        filename_guid = _ace_savedcar_guid_from_path(savedcar)
        if path_key in direct_seen:
            continue
        inspection = direct_inspections.get(path_key)
        if not isinstance(inspection, dict):
            inspection = official_saved_car_state.inspect_saved_car_file(savedcar)
        matched_guid = next(
            (
                guid
                for guid in direct_guids
                if inspection.get("valid")
                and official_saved_car_state.matches_official_livery_guid(inspection, guid)
            ),
            "",
        )
        if not matched_guid:
            if filename_guid in direct_guids:
                direct_errors.append(f"SavedCar GUID validation failed: {savedcar}")
            continue
        direct_seen.add(path_key)
        direct_hits.append(
            _ace_savedcar_thumbnail_status_from_hit(
                savedcar,
                matched_guid,
                "car_guid",
                inspection=inspection,
            )
        )

    if direct_hits:
        direct_hits.sort(
            key=lambda row: (
                bool(row.get("available")),
                str(row.get("saved_car_updated_at") or ""),
            ),
            reverse=True,
        )
        best = direct_hits[0]
        best["thumbnail_root"] = str(_ace_savedcars_thumbnail_root())
        best["matches"] = len(direct_hits)
        if direct_errors:
            best["errors"] = direct_errors[:5]
        return best

    if scan_index is None:
        scan_index = _build_savedcars_scan_index()

    candidates: list[tuple[str, str]] = []

    def add_candidate(kind: str, ref: str | None) -> None:
        rel = normalize_skin_asset_rel_path(str(ref or ""))
        if rel:
            candidates.append((kind, rel))

    add_candidate("visual_preset", str(files.get("visual_preset") or ""))
    add_candidate("skin_folder", str(files.get("skin_folder") or ""))

    seen_refs: set[str] = set()
    hits: list[dict[str, Any]] = []
    errors: list[str] = []
    seen_errors: set[str] = set()
    for kind, rel in candidates:
        refs = _my_skin_ref_candidates(entry, item_root, rel) if item_root else [rel]
        for ref in refs:
            key = norm_slashes(ref).lower()
            if not key or key in seen_refs:
                continue
            seen_refs.add(key)
            found, scan_errors = _scan_savedcars_for_path(ref, scan_index=scan_index)
            for error in scan_errors:
                if error in seen_errors:
                    continue
                seen_errors.add(error)
                errors.append(error)
            for savedcar in found:
                hits.append(_ace_savedcar_thumbnail_status_from_hit(savedcar, ref, kind))

    if not hits:
        message = "No AC EVO saved thumbnail found"
        if errors:
            message += f" ({errors[0]})"
        return {
            "available": False,
            "status": "not_found",
            "message": message,
            "thumbnail_root": str(_ace_savedcars_thumbnail_root()),
            "errors": errors[:5],
        }

    hits.sort(
        key=lambda row: (
            bool(row.get("available")),
            str(row.get("saved_car_updated_at") or ""),
        ),
        reverse=True,
    )
    best = hits[0]
    best["thumbnail_root"] = str(_ace_savedcars_thumbnail_root())
    best["matches"] = len(hits)
    if errors:
        best["errors"] = errors[:5]
    return best


def _my_skin_preview_source(entry: dict[str, Any]) -> str:
    source = str(entry.get("preview_source") or "").strip().lower()
    if source in {MY_SKIN_PREVIEW_SOURCE_CUSTOM, MY_SKIN_PREVIEW_SOURCE_ACE}:
        return source
    return MY_SKIN_PREVIEW_SOURCE_AUTO


def _file_signature(path: Path | None) -> dict[str, int]:
    if path is None:
        return {"size": 0, "mtime_ns": 0}
    try:
        stat = path.stat()
    except OSError:
        return {"size": 0, "mtime_ns": 0}
    return {"size": int(stat.st_size), "mtime_ns": int(stat.st_mtime_ns)}


def file_content_signature(path: str | Path) -> dict[str, Any]:
    target = Path(path)
    if not target.exists() or not target.is_file():
        return {
            "path": str(target),
            "exists": False,
            "size": 0,
            "mtimeNs": 0,
            "sha256": "",
            "signature": "",
            "stable": False,
        }
    before = target.stat()
    digest = hashlib.sha256()
    with target.open("rb") as handle:
        for chunk in iter(lambda: handle.read(1024 * 1024), b""):
            digest.update(chunk)
    after = target.stat()
    stable = int(before.st_size) == int(after.st_size) and int(before.st_mtime_ns) == int(after.st_mtime_ns)
    sha256 = digest.hexdigest()
    signature = f"{int(after.st_size)}:{int(after.st_mtime_ns)}:{sha256}" if stable else ""
    return {
        "path": str(target),
        "exists": True,
        "size": int(after.st_size),
        "mtimeNs": int(after.st_mtime_ns),
        "sha256": sha256,
        "signature": signature,
        "stable": stable,
    }


def ace_savedcar_thumbnail_content_signature(texture_path: str | Path) -> dict[str, Any]:
    texture = Path(texture_path)
    mips = texture.with_suffix(".texturemips")
    texture_signature = file_content_signature(texture)
    mips_signature = file_content_signature(mips)
    stable = bool(texture_signature.get("stable") and mips_signature.get("stable"))
    signature = (
        f"{texture_signature.get('signature')}|{mips_signature.get('signature')}"
        if stable
        else ""
    )
    return {
        "texture": texture_signature,
        "mips": mips_signature,
        "signature": signature,
        "stable": stable,
    }


def _effective_my_skin_thumbnail_path(
    entry: dict[str, Any],
    project_thumbnail: Path | None,
    local_thumbnail: Path | None,
) -> str:
    project_path = str(project_thumbnail) if project_thumbnail and project_thumbnail.exists() else ""
    local_path = str(local_thumbnail) if local_thumbnail and local_thumbnail.exists() else ""
    return project_path or local_path


def _content_quarantine_dir(root_path: Path, label: str) -> Path:
    safe_label = "".join(ch if ch.isalnum() or ch in "_-" else "_" for ch in label).strip("_") or "files"
    return get_program_root() / "_gamefile_quarantine" / safe_label / datetime.now().strftime("%Y%m%d_%H%M%S_%f")


def create_additional_rim_option(
    source_rimmesh: str | Path,
    target_compatiblerim: str | Path,
    actor_path: str | Path,
    cardesign_paths: list[str | Path],
    slot_id: str,
    display_name: str = "",
    root: str | Path | None = None,
) -> dict[str, Any]:
    root_path = normalize_game_root(root or Path.cwd())
    source = Path(source_rimmesh)
    target_compat = Path(target_compatiblerim)
    actor = Path(actor_path)
    cards = [Path(p) for p in cardesign_paths if str(p).strip()]
    clean_slot = "".join(ch for ch in str(slot_id).strip() if ch.isalnum() or ch in "_-").strip("_-")
    if not clean_slot:
        raise ValueError("New rim option id is required")
    if not source.exists() or source.suffix.lower() != ".rimmesh":
        raise FileNotFoundError(f"Source .rimmesh not found: {source}")
    if not target_compat.exists() or target_compat.suffix.lower() != ".compatiblerim":
        raise FileNotFoundError(f"Target .compatiblerim not found: {target_compat}")
    if not actor.exists() or actor.suffix.lower() != ".actor":
        raise FileNotFoundError(f"Actor file not found: {actor}")
    if not cards:
        raise ValueError("At least one .cardesign file is required")
    for card in cards:
        if not card.exists() or card.suffix.lower() != ".cardesign":
            raise FileNotFoundError(f"Cardesign file not found: {card}")

    actor_compat_refs = _content_refs_with_suffix(actor.read_bytes(), ".compatiblerim")
    if len(actor_compat_refs) < 2:
        raise ValueError(
            "Refusing to add a rim option to a car that does not already have multiple native rim options. "
            "For now, use the global test/swap workflow for single-rim cars.\n\n"
            f"Actor:\n{actor}\n\n"
            f"Existing rim options found: {len(actor_compat_refs)}"
        )

    target_refs = _compatiblerim_rimmesh_refs(target_compat.read_bytes())
    if not target_refs:
        raise ValueError("Target .compatiblerim does not contain a content-relative .rimmesh reference")
    target_rimmesh_refs = list(dict.fromkeys(target_refs))
    target_rimmeshes = [resolve_content_path(root_path, ref) for ref in target_rimmesh_refs]
    for ref, rimmesh in zip(target_rimmesh_refs, target_rimmeshes):
        if not rimmesh.exists():
            raise FileNotFoundError(f"Target .rimmesh from .compatiblerim not found: {ref}")

    new_compat = target_compat.parent / f"{clean_slot}.compatiblerim"
    new_compat_ref = game_rel_from_disk(root_path, new_compat)
    if not new_compat_ref:
        raise ValueError("New rim files must be inside the ACEvo content tree")
    require_game_write_ready(root_path, "create additional rim option", [new_compat, actor, *cards])

    rim_results: list[dict[str, Any]] = []
    rim_replacements: dict[str, str] = {}
    position_labels = ["front", "rear"]
    for index, (target_rimmesh_ref, target_rimmesh) in enumerate(zip(target_rimmesh_refs, target_rimmeshes)):
        source_info = inspect_rimmesh_readable(source, root_path)
        target_info = inspect_rimmesh_readable(target_rimmesh, root_path)
        compatibility = compare_rimmesh_compatibility(source_info, target_info)
        if not compatibility.get("ok", True):
            warnings = compatibility.get("warnings") or []
            warning_text = "\n".join(f"- {row}" for row in warnings) if warnings else "- incompatible rim dimensions"
            raise ValueError(
                "Refusing to create a rim option with incompatible rim dimensions. "
                "Pick a source rimmesh with the same diameter and width as the target rimmesh.\n\n"
                f"Target rimmesh:\n{target_rimmesh_ref}\n\n"
                f"Compatibility issues:\n{warning_text}"
            )
        if len(target_rimmesh_refs) == 1:
            new_rimmesh = target_compat.parent / f"{clean_slot}.rimmesh"
        else:
            suffix = position_labels[index] if index < len(position_labels) else str(index + 1)
            new_rimmesh = target_compat.parent / f"{clean_slot}_{suffix}.rimmesh"
        new_rimmesh_ref = game_rel_from_disk(root_path, new_rimmesh)
        if not new_rimmesh_ref:
            raise ValueError("New rim files must be inside the ACEvo content tree")
        rim_result = create_rimmesh_swap(source, target_rimmesh, new_rimmesh, root_path)
        if str(display_name).strip():
            renamed_data = _set_rimmesh_display_name(new_rimmesh.read_bytes(), display_name)
            if renamed_data != new_rimmesh.read_bytes():
                safe_write_bytes(new_rimmesh, renamed_data)
            rim_result.update(inspect_rimmesh_readable(new_rimmesh, root_path))
        rim_results.append(rim_result)
        rim_replacements[target_rimmesh_ref] = norm_slashes(new_rimmesh_ref)

    rim_result = rim_results[0]
    compat_data = patch_strings_recursive_exact(target_compat.read_bytes(), rim_replacements)
    compat_backup = safe_write_bytes(new_compat, compat_data)

    old_actor_data = actor.read_bytes()
    actor_data = _append_actor_compatible_rim(old_actor_data, norm_slashes(new_compat_ref))
    actor_backup = safe_write_bytes(actor, actor_data) if actor_data != old_actor_data else None

    cardesign_backups: list[dict[str, str]] = []
    for card in cards:
        old_data = card.read_bytes()
        new_data = _append_cardesign_compatible_rim(old_data, norm_slashes(new_compat_ref))
        backup = safe_write_bytes(card, new_data) if new_data != old_data else None
        cardesign_backups.append({"path": str(card), "backup": str(backup) if backup else ""})

    marker = _custom_rim_marker_path(new_compat)
    marker_data = {
        "type": "liverylab_custom_rim_option",
        "version": 1,
        "slot_id": clean_slot,
        "display_name": str(display_name).strip(),
        "compatiblerim": str(new_compat),
        "compatiblerim_ref": norm_slashes(new_compat_ref),
        "rimmeshes": [str(result.get("out") or "") for result in rim_results],
        "rimmesh_refs": list(rim_replacements.values()),
        "source_rimmesh": str(source),
        "target_compatiblerim": str(target_compat),
        "target_rimmesh_refs": target_rimmesh_refs,
        "actor": str(actor),
        "cardesigns": [str(card) for card in cards],
        "created_by": "LiveryLab",
        "created_at": datetime.now().isoformat(timespec="seconds"),
    }
    marker_backup = safe_write_text(marker, json.dumps(marker_data, indent=2, ensure_ascii=False))

    return {
        "source_rimmesh": str(source),
        "target_compatiblerim": str(target_compat),
        "target_rimmesh": str(target_rimmeshes[0]),
        "target_rimmeshes": [str(path) for path in target_rimmeshes],
        "new_rimmesh": str(Path(str(rim_results[0].get("out") or ""))),
        "new_rimmeshes": [str(result.get("out") or "") for result in rim_results],
        "new_compatiblerim": str(new_compat),
        "new_rimmesh_ref": rim_replacements[target_rimmesh_refs[0]],
        "new_rimmesh_refs": list(rim_replacements.values()),
        "new_compatiblerim_ref": norm_slashes(new_compat_ref),
        "rimmesh": rim_result,
        "rimmeshes": rim_results,
        "actor": str(actor),
        "actor_backup": str(actor_backup) if actor_backup else "",
        "compat_backup": str(compat_backup) if compat_backup else "",
        "cardesigns": cardesign_backups,
        "marker": str(marker),
        "marker_backup": str(marker_backup) if marker_backup else "",
        "compatibility": rim_result.get("compatibility") or {},
        "summary": (
            "Additional rim option created\n"
            f"id: {clean_slot}\n"
            f"compatiblerim: {new_compat}\n"
            f"rimmesh: {new_rimmesh}\n"
            f"registered: {norm_slashes(new_compat_ref)}"
        ),
    }


def remove_custom_rim_option(
    marker_path: str | Path,
    actor_path: str | Path,
    cardesign_paths: list[str | Path],
    root: str | Path | None = None,
) -> dict[str, Any]:
    root_path = normalize_game_root(root or Path.cwd())
    marker = Path(marker_path)
    actor = Path(actor_path)
    cards = [Path(p) for p in cardesign_paths if str(p).strip()]
    if not marker.exists() or marker.suffix.lower() != ".json" or not marker.name.endswith(".liverylab-rim.json"):
        raise FileNotFoundError(f"LiveryLab custom rim marker not found: {marker}")
    if not actor.exists() or actor.suffix.lower() != ".actor":
        raise FileNotFoundError(f"Actor file not found: {actor}")
    if not cards:
        raise ValueError("At least one .cardesign file is required")
    for card in cards:
        if not card.exists() or card.suffix.lower() != ".cardesign":
            raise FileNotFoundError(f"Cardesign file not found: {card}")

    data = json.loads(marker.read_text(encoding="utf-8"))
    if not isinstance(data, dict) or data.get("type") != "liverylab_custom_rim_option":
        raise ValueError("Marker is not a LiveryLab custom rim option marker")
    compat_ref = norm_slashes(str(data.get("compatiblerim_ref") or ""))
    compat = Path(str(data.get("compatiblerim") or ""))
    if not compat_ref or not compat.name.lower().endswith(".compatiblerim"):
        raise ValueError("Marker does not contain a valid custom .compatiblerim")
    if compat.exists() and _custom_rim_marker_path(compat).resolve() != marker.resolve():
        raise ValueError("Marker and .compatiblerim path do not match")
    if "skins" not in [part.lower() for part in compat.parts]:
        raise ValueError("Refusing to remove a rim option outside a skins folder")

    savedcar_hits, savedcar_scan_errors = _scan_savedcars_for_path(compat_ref)
    if savedcar_scan_errors:
        preview = "\n".join(f"- {row}" for row in savedcar_scan_errors[:12])
        extra = "" if len(savedcar_scan_errors) <= 12 else f"\n- ... and {len(savedcar_scan_errors) - 12} more"
        raise ValueError(
            "SavedCars could not be scanned reliably, so the rim option was not removed. "
            "Close ACE, check folder permissions, then try again.\n\n"
            f"Referenced rim option:\n{compat_ref}\n\n"
            f"Scan errors:\n{preview}{extra}"
        )
    if savedcar_hits:
        preview = "\n".join(f"- {path}" for path in savedcar_hits[:12])
        extra = "" if len(savedcar_hits) <= 12 else f"\n- ... and {len(savedcar_hits) - 12} more"
        raise ValueError(
            "This rim option is still used by ACE saved cars. "
            "Switch those cars back to a game-original rim in ACE or move the saved cars first.\n\n"
            f"Referenced rim option:\n{compat_ref}\n\n"
            f"Saved cars:\n{preview}{extra}"
        )
    require_game_write_ready(root_path, "remove custom rim option", [actor, compat, marker, *cards])

    old_actor_data = actor.read_bytes()
    new_actor_data, actor_changed = _remove_actor_compatible_rim(old_actor_data, compat_ref)
    actor_backup = safe_write_bytes(actor, new_actor_data) if actor_changed else None

    cardesign_results: list[dict[str, str | bool]] = []
    for card in cards:
        old_data = card.read_bytes()
        new_data, changed = _remove_cardesign_compatible_rim(old_data, compat_ref)
        backup = safe_write_bytes(card, new_data) if changed else None
        cardesign_results.append({"path": str(card), "changed": changed, "backup": str(backup) if backup else ""})

    deleted_dir = _content_quarantine_dir(root_path, "deleted_custom_rims")
    deleted_dir.mkdir(parents=True, exist_ok=True)
    moved: list[dict[str, str]] = []
    files_to_move = [compat]
    for path_text in data.get("rimmeshes") or []:
        path = Path(str(path_text))
        if path.exists():
            files_to_move.append(path)
    files_to_move.append(marker)
    seen: set[str] = set()
    for source_path in files_to_move:
        key = str(source_path.resolve()).lower() if source_path.exists() else str(source_path).lower()
        if key in seen or not source_path.exists():
            continue
        seen.add(key)
        try:
            source_path.resolve().relative_to(root_path.resolve())
        except Exception:
            raise ValueError(f"Refusing to move file outside ACEvo root: {source_path}")
        target = deleted_dir / source_path.name
        shutil.move(str(source_path), str(target))
        moved.append({"from": str(source_path), "to": str(target)})

    return {
        "marker": str(marker),
        "compatiblerim_ref": compat_ref,
        "actor": str(actor),
        "actor_changed": actor_changed,
        "actor_backup": str(actor_backup) if actor_backup else "",
        "cardesigns": cardesign_results,
        "deleted_dir": str(deleted_dir),
        "moved": moved,
        "summary": (
            "Custom rim option removed\n"
            f"compatiblerim: {compat_ref}\n"
            f"deleted files moved to: {deleted_dir}"
        ),
    }


def _rimmesh_patch_ref(path: str | Path, root: str | Path | None = None) -> str:
    p = Path(path)
    root_path = Path(root) if root else None
    if root_path and p.is_absolute():
        rel = content_rel_from_disk(root_path, p)
        if rel:
            return norm_slashes(rel)
    text = norm_slashes(str(path))
    if text.lower().startswith("content\\"):
        return text
    if root_path and not p.is_absolute():
        candidate = root_path / text.replace("\\", "/")
        rel = content_rel_from_disk(root_path, candidate)
        if rel:
            return norm_slashes(rel)
    return text


def patch_compatiblerim_for_test(compatiblerim_path: str | Path, target_rimmesh: str | Path, test_rimmesh: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    compat = Path(compatiblerim_path)
    if compat.suffix.lower() != ".compatiblerim":
        raise ValueError("Target file must be a .compatiblerim")
    if not compat.exists():
        raise FileNotFoundError(f"compatiblerim not found: {compat}")
    root_path = normalize_game_root(root or Path.cwd())
    writer = GameFileWriter(root_path, "patch compatiblerim for test", [compat])
    target_ref = _rimmesh_patch_ref(target_rimmesh, root)
    test_ref = _rimmesh_patch_ref(test_rimmesh, root)
    if not target_ref or not test_ref:
        raise ValueError("Target and test rimmesh paths are required")
    if not target_ref.lower().startswith("content\\") or not test_ref.lower().startswith("content\\"):
        raise ValueError("Rimmesh references must resolve to content-relative ACEvo paths")
    data = compat.read_bytes()
    new_data = patch_strings_recursive_exact(data, {target_ref: test_ref})
    if new_data == data:
        if test_ref.encode("utf-8") in data:
            return {
                "compatiblerim": str(compat),
                "backup": "",
                "target_ref": target_ref,
                "test_ref": test_ref,
                "changed": False,
                "summary": f"compatiblerim already points to test rimmesh\ncompatiblerim: {compat}\ntest: {test_ref}",
            }
        raise ValueError(f"Target rimmesh reference not found in compatiblerim: {target_ref}")
    write_result = writer.write_bytes(compat, new_data, label="compatiblerim", backup=True)
    return {
        "compatiblerim": str(compat),
        "backup": write_result.backup,
        "target_ref": target_ref,
        "test_ref": test_ref,
        "changed": True,
        "writeResult": write_result.to_dict(),
        "writer": writer.to_dict(),
        "summary": f"Patched compatiblerim\ncompatiblerim: {compat}\nbackup: {write_result.backup or '-'}\nold: {target_ref}\nnew: {test_ref}",
    }


def restore_compatiblerim_backup(compatiblerim_path: str | Path, backup_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    compat = Path(compatiblerim_path)
    backup = Path(backup_path)
    if compat.suffix.lower() != ".compatiblerim":
        raise ValueError("Target file must be a .compatiblerim")
    if not backup.exists():
        raise FileNotFoundError(f"Backup not found: {backup}")
    if backup.suffix.lower() != ".bak":
        raise ValueError("Backup file must be a .bak")
    writer = None
    if root:
        writer = GameFileWriter(normalize_game_root(root), "restore compatiblerim backup", [compat])
    write_result = writer.copy_file(backup, compat, label="compatiblerim", backup=True) if writer else None
    if write_result is None:
        current_backup = backup_file(compat) if compat.exists() else None
        compat.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(backup, compat)
        previous_backup = str(current_backup) if current_backup else ""
    else:
        previous_backup = write_result.backup
    return {
        "compatiblerim": str(compat),
        "restored_from": str(backup),
        "previous_current_backup": previous_backup,
        "writeResult": write_result.to_dict() if write_result else {},
        "writer": writer.to_dict() if writer else {},
        "summary": f"Restored compatiblerim\ncompatiblerim: {compat}\nrestored from: {backup}\ncurrent-state backup: {previous_backup or '-'}",
    }


def compare_rimmesh_compatibility(source: dict[str, Any], target: dict[str, Any]) -> dict[str, Any]:
    warnings: list[str] = []
    ok = True
    for key, label in [("diameter", "diameter"), ("width", "width")]:
        if source.get(key) != target.get(key):
            ok = False
            warnings.append(f"Different {label}: source {source.get(key) or '-'} / target {target.get(key) or '-'}")
    if source.get("offset") != target.get("offset"):
        warnings.append(f"Different offset: source {source.get('offset') if source.get('offset') is not None else '-'} / target {target.get('offset') if target.get('offset') is not None else '-'}")
    return {"ok": ok, "warnings": warnings}


def _parse_obj_index(token: str, count: int) -> int:
    value = int(token)
    if value < 0:
        value = count + value + 1
    return value - 1


def parse_obj_geometry(obj_path: str | Path) -> dict[str, Any]:
    path = Path(obj_path)
    positions: list[tuple[float, float, float]] = []
    texcoords_raw: list[tuple[float, float]] = []
    normals_raw: list[tuple[float, float, float]] = []
    faces: list[list[tuple[int, int | None, int | None]]] = []
    for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
        stripped = line.strip()
        if not stripped or stripped.startswith("#"):
            continue
        parts = stripped.split()
        if not parts:
            continue
        tag = parts[0].lower()
        if tag == "v" and len(parts) >= 4:
            positions.append((float(parts[1]), float(parts[2]), float(parts[3])))
        elif tag == "vt" and len(parts) >= 3:
            texcoords_raw.append((float(parts[1]), float(parts[2])))
        elif tag == "vn" and len(parts) >= 4:
            normals_raw.append((float(parts[1]), float(parts[2]), float(parts[3])))
        elif tag == "f" and len(parts) >= 4:
            face: list[tuple[int, int | None, int | None]] = []
            for token in parts[1:]:
                chunks = token.split("/")
                vi = _parse_obj_index(chunks[0], len(positions))
                vti = _parse_obj_index(chunks[1], len(texcoords_raw)) if len(chunks) > 1 and chunks[1] else None
                vni = _parse_obj_index(chunks[2], len(normals_raw)) if len(chunks) > 2 and chunks[2] else None
                face.append((vi, vti, vni))
            for idx in range(1, len(face) - 1):
                faces.append([face[0], face[idx], face[idx + 1]])

    indices: list[int] = []
    uvs: list[tuple[float, float] | None] = [None] * len(positions)
    normals: list[tuple[float, float, float] | None] = [None] * len(positions)
    for face in faces:
        for vi, vti, vni in face:
            if vi < 0 or vi >= len(positions):
                raise ValueError(f"OBJ face references invalid vertex index {vi + 1}")
            indices.append(vi)
            if vti is not None:
                if vti < 0 or vti >= len(texcoords_raw):
                    raise ValueError(f"OBJ face references invalid UV index {vti + 1}")
                current = uvs[vi]
                value = texcoords_raw[vti]
                if current is not None and current != value:
                    raise ValueError("OBJ uses multiple UVs for the same vertex. Split vertices are not supported by template reimport.")
                uvs[vi] = value
            if vni is not None:
                if vni < 0 or vni >= len(normals_raw):
                    raise ValueError(f"OBJ face references invalid normal index {vni + 1}")
                current = normals[vi]
                value = normals_raw[vni]
                if current is not None and current != value:
                    raise ValueError("OBJ uses multiple normals for the same vertex. Split vertices are not supported by template reimport.")
                normals[vi] = value

    return {
        "positions": positions,
        "uvs": [row if row is not None else (0.0, 0.0) for row in uvs],
        "normals": [row if row is not None else (0.0, 0.0, 0.0) for row in normals],
        "indices": indices,
        "has_uvs": any(row is not None for row in uvs),
        "has_normals": any(row is not None for row in normals),
    }


def _mesh_update_main_payload(raw: bytes, obj: dict[str, Any], template: dict[str, Any]) -> bytes:
    fields = parse_fields(raw)
    new_fields: list[ProtoField] = []
    for field in fields:
        if field.number == 5 and field.wire == 2:
            new_fields.append(ProtoField(field.number, field.wire, _mesh_pack_float_vectors(obj["positions"], 3)))
        elif field.number == 6 and field.wire == 2 and obj.get("has_normals"):
            new_fields.append(ProtoField(field.number, field.wire, _mesh_pack_float_vectors(obj["normals"], 3)))
        elif field.number == 7 and field.wire == 2 and obj.get("has_uvs"):
            new_fields.append(ProtoField(field.number, field.wire, _mesh_pack_float_vectors(obj["uvs"], 2)))
        elif field.number == 11 and field.wire == 2:
            new_fields.append(ProtoField(field.number, field.wire, _mesh_encode_index_buffer(obj["indices"])))
        else:
            new_fields.append(field)
    return build_fields(new_fields)


def _mesh_update_bounds(raw: bytes, values: list[float]) -> bytes:
    fields = parse_fields(raw)
    value_index = 0
    new_fields: list[ProtoField] = []
    for field in fields:
        if field.wire == 5 and value_index < len(values):
            new_fields.append(ProtoField(field.number, field.wire, struct.pack("<f", float(values[value_index]))))
            value_index += 1
        else:
            new_fields.append(field)
    return build_fields(new_fields)


def import_obj_to_mesh_template(template_mesh: str | Path, obj_path: str | Path, out_mesh: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    template_path = Path(template_mesh)
    obj = parse_obj_geometry(obj_path)
    template = inspect_mesh_readable(template_path, root=root)
    if len(obj["positions"]) != int(template.get("vertex_count") or 0):
        raise ValueError(f"OBJ vertex count ({len(obj['positions'])}) must match template vertex count ({template.get('vertex_count')})")
    if len(obj["indices"]) != int(template.get("index_count") or 0):
        raise ValueError(f"OBJ index count ({len(obj['indices'])}) must match template index count ({template.get('index_count')})")
    if template.get("normal_count") and obj.get("has_normals") and len(obj["normals"]) != int(template.get("normal_count") or 0):
        raise ValueError("OBJ normal count must match template normal count")
    if template.get("uv_count") and obj.get("has_uvs") and len(obj["uvs"]) != int(template.get("uv_count") or 0):
        raise ValueError("OBJ UV count must match template UV count")

    data = template_path.read_bytes()
    top_fields = parse_fields(data)
    if not obj["positions"]:
        raise ValueError("OBJ contains no vertices")
    xs = [row[0] for row in obj["positions"]]
    ys = [row[1] for row in obj["positions"]]
    zs = [row[2] for row in obj["positions"]]
    bounds_min = [min(xs), min(ys), min(zs)]
    bounds_max = [max(xs), max(ys), max(zs)]
    new_top: list[ProtoField] = []
    for field in top_fields:
        if field.number == 5 and field.wire == 2:
            new_top.append(ProtoField(field.number, field.wire, _mesh_update_main_payload(bytes(field.value), obj, template)))
        elif field.number == 6 and field.wire == 2:
            new_top.append(ProtoField(field.number, field.wire, _mesh_update_bounds(bytes(field.value), bounds_min)))
        elif field.number == 7 and field.wire == 2:
            new_top.append(ProtoField(field.number, field.wire, _mesh_update_bounds(bytes(field.value), bounds_max)))
        else:
            new_top.append(field)

    out = Path(out_mesh)
    if out.exists():
        backup_file(out)
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_bytes(build_fields(new_top))
    return {
        "mesh_path": str(out),
        "template_path": str(template_path),
        "obj_path": str(obj_path),
        "vertex_count": len(obj["positions"]),
        "index_count": len(obj["indices"]),
        "triangle_count": len(obj["indices"]) // 3,
        "bounds_min": bounds_min,
        "bounds_max": bounds_max,
    }


def inspect_mesh_uv_view(mesh_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    mesh = inspect_mesh_readable(mesh_path, root=root)
    root_path = Path(root) if root else None
    material: dict[str, Any] | None = None
    texture_slots: list[dict[str, Any]] = []
    material_ranges: list[dict[str, Any]] = []
    material_diagnostics: list[dict[str, Any]] = []
    inspected_materials: dict[str, dict[str, Any] | None] = {}
    material_ref = str(mesh.get("material_path") or "")
    resolved_material_path = Path(str(mesh.get("resolved_material_path"))) if mesh.get("resolved_material_path") else None
    if root_path and material_ref and resolved_material_path and resolved_material_path.exists():
        try:
            material = inspect_material_readable(resolved_material_path, root=root_path)
        except Exception:
            material = None
    if material:
        for slot in material.get("texture_references", []):
            resolved_path = str(slot.get("resolved_path") or "")
            suffix = Path(resolved_path or str(slot.get("path") or "")).suffix.lower()
            texture_slots.append(
                {
                    "name": slot.get("name"),
                    "path": slot.get("path"),
                    "resolved_path": resolved_path or None,
                    "preview_supported": suffix in {".png", ".jpg", ".jpeg", ".bmp", ".webp", ".gif", ".tga", ".tif", ".tiff"},
                }
            )
    offset = 0
    for block_index, block in enumerate(mesh.get("material_blocks", []) or []):
        count = max(0, int(block.get("index_count") or 0))
        if not count:
            continue
        start_triangle = offset // 3
        triangle_count = count // 3
        name = str(block.get("name") or "")
        material_name = str(block.get("material") or "")
        block_material_ref = str(block.get("material_path") or material_name or "")
        block_material_path = resolve_content_path(root_path, block_material_ref) if root_path and block_material_ref else None
        block_material_key = str(block_material_path or block_material_ref)
        block_material_exists = bool(block_material_path and block_material_path.exists())
        block_material_info: dict[str, Any] | None = None
        block_search_text = f"{name} {block_material_ref}".lower()
        inspect_block_material = any(token in block_search_text for token in ("window", "windscreen", "windshield", "glass", "banner"))
        if inspect_block_material and block_material_exists:
            if block_material_key not in inspected_materials:
                try:
                    inspected_materials[block_material_key] = inspect_material(block_material_path, root=root_path)
                except Exception:
                    inspected_materials[block_material_key] = None
            block_material_info = inspected_materials.get(block_material_key)
            if block_material_info:
                relevant_slots = []
                for slot in block_material_info.get("texture_slots", []):
                    slot_name = str(slot.get("name") or "")
                    value = slot.get("value")
                    if (
                        value not in (None, "", [])
                        or slot_name.lower() in {"txdiffuse", "opacitymap", "normalmap", "reflectancemap", "base_basecolormap"}
                    ):
                        relevant_slots.append(
                            {
                                "name": slot.get("name"),
                                "value": value,
                                "is_referenced": bool(slot.get("is_referenced")),
                                "resolved_path": slot.get("resolved_path"),
                            }
                        )
                relevant_parameters = []
                for param in block_material_info.get("parameters", []):
                    param_name = str(param.get("name") or "")
                    if param.get("value") in (None, [], ""):
                        continue
                    if any(token in param_name.lower() for token in ("opacity", "trans", "basecolor", "uvscale", "windshield")):
                        relevant_parameters.append({"name": param.get("name"), "value": param.get("value")})
                material_diagnostics.append(
                    {
                        "rangeIndex": block_index,
                        "name": name,
                        "material_path": block_material_ref or None,
                        "resolved_material_path": str(block_material_path) if block_material_path else None,
                        "exists": block_material_exists,
                        "shader": block_material_info.get("shader"),
                        "parent_material_path": block_material_info.get("material_path"),
                        "texture_slots": relevant_slots,
                        "parameters": relevant_parameters,
                    }
                )
        name_upper = name.upper()
        material_ranges.append(
            {
                "index": block_index,
                "name": name,
                "material": block_material_ref or material_name,
                "materialPath": block_material_ref or None,
                "resolvedMaterialPath": str(block_material_path) if block_material_path else None,
                "materialExists": block_material_exists,
                "shader": block_material_info.get("shader") if block_material_info else None,
                "startIndex": offset,
                "indexCount": count,
                "startTriangle": start_triangle,
                "triangleCount": triangle_count,
                "skinRelevant": "SKIN" in name_upper,
            }
        )
        offset += count
    return {
        "file": mesh.get("file"),
        "mesh_name": mesh.get("mesh_name"),
        "material_path": mesh.get("material_path"),
        "resolved_material_path": mesh.get("resolved_material_path"),
        "material_exists": mesh.get("material_exists"),
        "vertex_count": mesh.get("vertex_count"),
        "triangle_count": mesh.get("triangle_count"),
        "uv_count": mesh.get("uv_count"),
        "bounds_min": mesh.get("bounds_min"),
        "bounds_max": mesh.get("bounds_max"),
        "submeshes": mesh.get("submeshes", []),
        "material_ranges": material_ranges,
        "material_diagnostics": material_diagnostics,
        "texture_slots": texture_slots,
        "uvs": [[float(u), float(v)] for u, v in mesh.get("uvs", [])],
        "indices": [int(index) for index in mesh.get("indices", [])],
    }


CAR_LIVERY_COMPATIBILITY_CACHE_FILENAME = "car_livery_compatibility.json"
SKIN_TEMPLATE_UNSUPPORTED_MESSAGE = (
    "Unsupported template: this AC EVO cardesign is not connected to a complete visual/mechanical preset chain."
)


def get_car_livery_compatibility_cache_path() -> Path:
    return get_livery_library_root() / "Settings" / CAR_LIVERY_COMPATIBILITY_CACHE_FILENAME


def _root_relative_path(root: Path, path: Path | None) -> str:
    if path is None:
        return ""
    try:
        return norm_slashes(str(path.resolve().relative_to(root.resolve())))
    except Exception:
        return str(path)


def _resolve_template_path(root: Path, value: str | Path) -> Path:
    path = Path(value)
    if path.is_absolute():
        return path
    text = norm_slashes(str(value))
    if text.lower().startswith("content\\"):
        return resolve_content_path(root, text)
    return (root / path).resolve()


def inspect_skin_template_support(root: str | Path, cardesign: str | Path, design: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    cardesign_path = _resolve_template_path(game_root, cardesign)
    result: dict[str, Any] = {
        "status": "unknown",
        "supported": False,
        "message": "",
        "reasons": [],
        "missing": [],
        "carId": "",
        "cardesign": _root_relative_path(game_root, cardesign_path),
        "cardesignPath": str(cardesign_path),
        "design": "",
        "visualPreset": "",
        "mechanicalPreset": "",
        "carFinalState": "",
        "actor": "",
        "actorCardesignRegistered": False,
        "actorVisualRegistered": False,
    }
    try:
        if not cardesign_path.exists():
            result["status"] = "unsupported"
            result["message"] = SKIN_TEMPLATE_UNSUPPORTED_MESSAGE
            result["missing"].append("Cardesign file")
            result["reasons"].append("Cardesign file does not exist.")
            return result

        cardesign_rel = content_rel_from_disk(game_root, cardesign_path) or _root_relative_path(game_root, cardesign_path)
        cardetails = extract_cardesign_details(cardesign_path.read_bytes())
        design_path = _resolve_template_path(game_root, design) if design else None
        if design_path is None or not design_path.exists():
            design_rel = str(cardetails.get("design_path") or "").strip()
            design_path = resolve_content_path(game_root, design_rel) if design_rel else cardesign_path.with_suffix(".design")
        result["design"] = _root_relative_path(game_root, design_path)

        if not design_path.exists():
            result["status"] = "unsupported"
            result["message"] = SKIN_TEMPLATE_UNSUPPORTED_MESSAGE
            result["missing"].append("Design file")
            result["reasons"].append("The cardesign does not resolve to an existing .design file.")
            return result

        info = extract_design_info(cardesign_path.read_bytes(), design_path.read_bytes())
        car_content_path = info.car_content_path or ""
        if not car_content_path:
            parts = split_content_path(cardesign_rel)
            if len(parts) >= 3:
                car_content_path = norm_slashes(str(Path(*parts[:3])))
        car_id = split_content_path(car_content_path)[-1] if car_content_path else ""
        result["carId"] = car_id

        actor_path = find_actor_file(game_root, car_content_path) if car_content_path else None
        result["actor"] = _root_relative_path(game_root, actor_path)
        if not actor_path or not actor_path.exists():
            result["missing"].append("Actor file")
            result["reasons"].append("No actor file was found for the template car.")
        else:
            actor_data = actor_path.read_bytes()
            actor_cardesigns = {norm_slashes(str(rel)).lower() for rel in actor_list_cardesigns(actor_data)}
            actor_visuals = {norm_slashes(str(rel)).lower() for rel in actor_list_visualpresets(actor_data)}
            result["actorCardesignRegistered"] = norm_slashes(cardesign_rel).lower() in actor_cardesigns
            if not result["actorCardesignRegistered"]:
                result["missing"].append("Actor cardesign registration")
                result["reasons"].append("The cardesign is not registered in the car actor.")

            visual_path = find_visualpreset_for_cardesign(game_root, actor_path, cardesign_path, cardesign_rel)
            visual_rel = content_rel_from_disk(game_root, visual_path) if visual_path else ""
            result["visualPreset"] = visual_rel or _root_relative_path(game_root, visual_path)
            if not visual_path or not visual_path.exists() or not visual_rel:
                result["missing"].append("Visual preset")
                result["reasons"].append("No visual preset references this cardesign.")
            else:
                result["actorVisualRegistered"] = norm_slashes(visual_rel).lower() in actor_visuals
                if not result["actorVisualRegistered"]:
                    result["missing"].append("Actor visual preset registration")
                    result["reasons"].append("The visual preset is not registered in the car actor.")

                mechanical_path = find_mechanicalpreset_for_visual(game_root, actor_path, visual_path, visual_rel)
                mechanical_rel = content_rel_from_disk(game_root, mechanical_path) if mechanical_path else ""
                result["mechanicalPreset"] = mechanical_rel or _root_relative_path(game_root, mechanical_path)
                if not mechanical_path or not mechanical_path.exists() or not mechanical_rel:
                    result["missing"].append("Mechanical preset")
                    result["reasons"].append("No mechanical preset references this visual preset.")
                else:
                    finalstate_path = find_carfinalstate_for_visual(game_root, visual_rel, mechanical_rel)
                    result["carFinalState"] = _root_relative_path(game_root, finalstate_path)
                    if not finalstate_path or not finalstate_path.exists():
                        result["missing"].append("Car final state")
                        result["reasons"].append("No car final state exists for the visual/mechanical preset pair.")

        if result["missing"]:
            result["status"] = "unsupported"
            result["message"] = SKIN_TEMPLATE_UNSUPPORTED_MESSAGE
        else:
            result["status"] = "ready"
            result["supported"] = True
            result["message"] = "Skin template is connected to a complete visual/mechanical preset chain."
            result["reasons"].append("Cardesign, visual preset, mechanical preset and car final state are linked.")
        return result
    except Exception as exc:
        result["status"] = "unknown"
        result["message"] = f"Skin template support could not be inspected: {exc}"
        result["reasons"].append(result["message"])
        return result


def _skin_template_cardesign_paths(game_root: Path) -> list[Path]:
    cars_dir = game_root / "content" / "cars"
    if not cars_dir.exists():
        return []
    paths: list[Path] = []
    for skins_dir in sorted(cars_dir.glob("*/skins"), key=lambda item: str(item).lower()):
        paths.extend(path for path in skins_dir.rglob("*.cardesign") if path.is_file())
    return [path for path in sorted(paths, key=lambda item: str(item).lower()) if "_acevo_backups" not in str(path).lower()]


def analyze_skin_template_support_inventory(
    root: str | Path,
    progress: Any | None = None,
    progress_offset: int = 0,
    progress_total: int | None = None,
    template_paths: Iterable[Path] | None = None,
    car_counts: dict[str, int] | None = None,
) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    paths = list(template_paths) if template_paths is not None else _skin_template_cardesign_paths(game_root)
    templates: list[dict[str, Any]] = []
    counts: dict[str, int] = {}
    total = int(progress_total or (progress_offset + len(paths)))
    base_counts = dict(car_counts or {})
    if not paths:
        if progress:
            progress(
                {
                    "phase": "checking_templates",
                    "current": "No skin templates found",
                    "index": int(progress_offset),
                    "total": total,
                    "counts": base_counts,
                    "templateCounts": counts,
                }
            )
        return {"counts": counts, "templates": [], "byCar": {}}

    if progress:
        progress(
            {
                "phase": "checking_templates",
                "current": "Preparing template checks",
                "index": int(progress_offset),
                "total": total,
                "counts": base_counts,
                "templateCounts": counts,
            }
        )

    for index, cardesign_path in enumerate(paths, start=1):
        item = inspect_skin_template_support(game_root, cardesign_path)
        status = str(item.get("status") or "unknown")
        counts[status] = counts.get(status, 0) + 1
        templates.append(_json_ready(item))
        if progress:
            car_id = str(item.get("carId") or "").strip() or cardesign_path.parent.parent.name
            progress(
                {
                    "phase": "checking_templates",
                    "current": f"{car_id}/{cardesign_path.name}",
                    "index": int(progress_offset) + index,
                    "total": total,
                    "counts": base_counts,
                    "templateCounts": dict(counts),
                }
            )
    by_car: dict[str, list[dict[str, Any]]] = {}
    for item in templates:
        car_id = str(item.get("carId") or "").strip()
        if not car_id:
            continue
        by_car.setdefault(car_id, []).append(item)
    return {"counts": counts, "templates": templates, "byCar": by_car}


def car_livery_inventory(root: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    cars_dir = game_root / "content" / "cars"
    car_ids: list[str] = []
    if cars_dir.is_dir():
        car_ids = [
            car_dir.name
            for car_dir in sorted(cars_dir.iterdir(), key=lambda path: path.name.lower())
            if car_dir.is_dir() and car_dir.name not in IGNORED_CAR_DIRS
        ]
    key_data = {
        "root": str(game_root).lower(),
        "carIds": [car_id.lower() for car_id in car_ids],
    }
    fingerprint = hashlib.sha1(json.dumps(key_data, sort_keys=True).encode("utf-8")).hexdigest()[:24]
    return {
        "root": str(game_root),
        "carsDir": str(cars_dir),
        "carCount": len(car_ids),
        "carIds": car_ids,
        "fingerprint": fingerprint,
    }


def _car_livery_cache_status(analysis: dict[str, Any] | None, inventory: dict[str, Any] | None) -> dict[str, Any]:
    if not isinstance(analysis, dict):
        return {
            "exists": False,
            "updateNeeded": False,
            "reason": "No cached analysis found",
            "newCars": [],
            "missingCars": [],
        }
    cached_inventory = analysis.get("carInventory") if isinstance(analysis.get("carInventory"), dict) else {}
    cached_ids = {str(car_id).lower() for car_id in cached_inventory.get("carIds", []) if str(car_id).strip()}
    current_ids = {str(car_id).lower() for car_id in (inventory or {}).get("carIds", []) if str(car_id).strip()}
    cached_display = {str(car_id).lower(): str(car_id) for car_id in cached_inventory.get("carIds", []) if str(car_id).strip()}
    current_display = {str(car_id).lower(): str(car_id) for car_id in (inventory or {}).get("carIds", []) if str(car_id).strip()}
    new_keys = sorted(current_ids - cached_ids)
    missing_keys = sorted(cached_ids - current_ids)
    root_changed = bool(inventory and cached_inventory.get("root") and str(cached_inventory.get("root")) != str(inventory.get("root")))
    cached_app_version = str(analysis.get("appVersion") or "").strip()
    current_app_version = str(APP_VERSION or "").strip()
    normalized_cached_app_version = cached_app_version.lower().removeprefix("v")
    normalized_current_app_version = current_app_version.lower().removeprefix("v")
    app_version_changed = normalized_cached_app_version != normalized_current_app_version
    update_needed = bool(root_changed or app_version_changed or new_keys or missing_keys)
    if root_changed:
        reason = "Game root changed. Run Analyze Cars again."
    elif app_version_changed:
        reason = "LiveryLab was updated. Run Analyze Cars again."
    elif new_keys:
        reason = "New cars detected. Run Analyze Cars again."
    elif missing_keys:
        reason = "Cached cars are no longer present. Run Analyze Cars again."
    else:
        reason = "Cached analysis is up to date."
    return {
        "exists": True,
        "updateNeeded": update_needed,
        "reason": reason,
        "newCars": [current_display.get(key, key) for key in new_keys],
        "missingCars": [cached_display.get(key, key) for key in missing_keys],
        "rootChanged": root_changed,
        "appVersionChanged": app_version_changed,
        "cachedAppVersion": cached_app_version,
        "currentAppVersion": current_app_version,
        "cachedFingerprint": str(cached_inventory.get("fingerprint") or ""),
        "currentFingerprint": str((inventory or {}).get("fingerprint") or ""),
    }


def load_car_livery_compatibility_cache(root: str | Path | None = None) -> dict[str, Any]:
    path = get_car_livery_compatibility_cache_path()
    analysis: dict[str, Any] | None = None
    exists = path.exists()
    if exists:
        try:
            data = json.loads(path.read_text(encoding="utf-8"))
            analysis = data if isinstance(data, dict) else None
        except Exception:
            analysis = None
    inventory = car_livery_inventory(root or get_default_root_folder() or Path.cwd()) if (root or get_default_root_folder()) else None
    cache_status = _car_livery_cache_status(analysis, inventory)
    cache_status["path"] = str(path)
    if analysis is not None:
        analysis["cacheStatus"] = _json_ready(cache_status)
    return {
        "analysis": _json_ready(analysis) if analysis else None,
        "inventory": _json_ready(inventory) if inventory else None,
        "cacheStatus": _json_ready(cache_status),
        "path": str(path),
        "exists": exists,
    }


def save_car_livery_compatibility_cache(root: str | Path, analysis: dict[str, Any]) -> dict[str, Any]:
    inventory = car_livery_inventory(root)
    now = datetime.now().isoformat(timespec="seconds")
    cached = {
        **(analysis if isinstance(analysis, dict) else {}),
        "formatVersion": 1,
        "analyzedAt": now,
        "appVersion": APP_VERSION,
        "root": str(inventory.get("root") or root),
        "rootFingerprint": str(inventory.get("fingerprint") or ""),
        "carInventory": _json_ready(inventory),
    }
    cached["cacheStatus"] = _json_ready(_car_livery_cache_status(cached, inventory))
    path = get_car_livery_compatibility_cache_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    backup = safe_write_text(path, json.dumps(_json_ready(cached), indent=2, ensure_ascii=False))
    return {
        "analysis": _json_ready(cached),
        "inventory": _json_ready(inventory),
        "cacheStatus": _json_ready(cached.get("cacheStatus") or {}),
        "path": str(path),
        "backup": str(backup) if backup else "",
    }


def _advanced_creator_excluded_cardesign_paths(game_root: Path) -> set[str]:
    excluded: set[str] = set()

    def add(value: str | Path | None) -> None:
        text = str(value or "").strip()
        if not text:
            return
        excluded.add(norm_slashes(text).lower())

    for entry in load_my_skins():
        if not isinstance(entry, dict):
            continue
        managed_by = str(entry.get("managed_by") or "").strip().lower()
        if managed_by and managed_by != MY_SKIN_MANAGED_BY_LIVERYLAB:
            continue
        if not managed_by and not bool(entry.get("liverylab_managed")):
            continue
        files = entry.get("files") if isinstance(entry.get("files"), dict) else {}
        cardesign_rel = normalize_skin_asset_rel_path(str(files.get("cardesign") or ""))
        if not cardesign_rel:
            continue
        add(cardesign_rel)
        if cardesign_rel.lower().startswith("content\\"):
            add(resolve_content_path(game_root, cardesign_rel))
        local_root = _entry_local_root(entry)
        if local_root is not None:
            add(local_root / Path(*split_content_path(cardesign_rel)))
    return excluded


def _advanced_creator_original_cardesign_paths(game_root: Path) -> set[str]:
    package_status = package_state.content_package_status(game_root, get_package_state_manifest_root())
    package_paths = []
    for raw in [package_status.get("activePath"), *(package_status.get("disabledPaths") or [])]:
        path = Path(str(raw or ""))
        if path.exists() and path.is_file():
            package_paths.append(path)
    originals: set[str] = set()

    def add(value: str | Path | None) -> None:
        text = str(value or "").strip()
        if not text:
            return
        originals.add(norm_slashes(text).lower())

    for package_path in package_paths:
        try:
            entries = read_pack_entries(package_path)
        except Exception:
            continue
        for entry in entries:
            if entry.is_directory:
                continue
            rel = norm_slashes(entry.name)
            rel_key = rel.lower()
            if not rel_key.startswith("content\\cars\\") or not rel_key.endswith(".cardesign"):
                continue
            add(rel)
            add(resolve_content_path(game_root, rel))
    return originals


def _advanced_creator_template_keys(game_root: Path, template: dict[str, Any]) -> set[str]:
    keys: set[str] = set()

    def add(value: str | Path | None) -> None:
        text = str(value or "").strip()
        if not text:
            return
        key = norm_slashes(text).lower()
        keys.add(key)
        content_index = key.find("content\\cars\\")
        if content_index >= 0:
            keys.add(key[content_index:])

    cardesign_path = template.get("cardesignPath") or ""
    cardesign_rel = template.get("cardesign") or ""
    add(cardesign_path)
    add(cardesign_rel)
    if cardesign_rel.lower().startswith("content\\"):
        add(resolve_content_path(game_root, cardesign_rel))
    if cardesign_path:
        add(_root_relative_path(game_root, Path(str(cardesign_path))))
    return keys


def _advanced_creator_filter_original_templates(
    game_root: Path,
    templates: list[dict[str, Any]],
    *,
    excluded_cardesign_paths: set[str],
    original_cardesign_paths: set[str],
) -> list[dict[str, Any]]:
    filtered: list[dict[str, Any]] = []
    for template in templates:
        if not isinstance(template, dict):
            continue
        keys = _advanced_creator_template_keys(game_root, template)
        if keys & excluded_cardesign_paths:
            continue
        if original_cardesign_paths and not (keys & original_cardesign_paths):
            continue
        filtered.append(template)
    return filtered


def analyze_car_livery_compatibility(root: str | Path | None = None, progress: Any | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root or get_default_root_folder() or Path.cwd())
    inventory = car_livery_inventory(game_root)
    car_total = int(inventory.get("carCount") or 0)
    template_paths = _skin_template_cardesign_paths(game_root)
    template_total = len(template_paths)
    combined_total = max(1, car_total + template_total + 1)

    def _combined_car_progress(payload: dict[str, Any]) -> None:
        if not progress:
            return
        phase = str(payload.get("phase") or "")
        if phase == "done":
            return
        mapped = dict(payload)
        try:
            index = int(mapped.get("index") or 0)
        except (TypeError, ValueError):
            index = 0
        mapped["index"] = max(0, min(index, car_total))
        mapped["total"] = combined_total
        progress(mapped)

    analysis = analyze_cars(game_root, inspect_mesh_readable, extract_design_slot_materials, inspect_material, progress=_combined_car_progress)
    car_counts = dict(analysis.get("counts") or {})
    template_inventory = analyze_skin_template_support_inventory(
        game_root,
        progress=progress,
        progress_offset=car_total,
        progress_total=combined_total,
        template_paths=template_paths,
        car_counts=car_counts,
    )
    excluded_cardesign_paths = _advanced_creator_excluded_cardesign_paths(game_root)
    original_cardesign_paths = _advanced_creator_original_cardesign_paths(game_root)
    filtered_templates = _advanced_creator_filter_original_templates(
        game_root,
        list(template_inventory.get("templates") or []),
        excluded_cardesign_paths=excluded_cardesign_paths,
        original_cardesign_paths=original_cardesign_paths,
    )
    filtered_by_car: dict[str, list[dict[str, Any]]] = {}
    for item in filtered_templates:
        car_id = str(item.get("carId") or "").strip()
        if car_id:
            filtered_by_car.setdefault(car_id, []).append(item)
    for car in analysis.get("cars", []):
        if not isinstance(car, dict):
            continue
        car_id = str(car.get("carId") or "").strip()
        templates = list(filtered_by_car.get(car_id, []))
        unsupported = [item for item in templates if str(item.get("status") or "") != "ready"]
        car["skinTemplates"] = _json_ready(templates)
        metrics = car.get("metrics") if isinstance(car.get("metrics"), dict) else {}
        metrics["skinTemplateCount"] = len(templates)
        metrics["skinTemplateReadyCount"] = len(templates) - len(unsupported)
        metrics["skinTemplateUnsupportedCount"] = len(unsupported)
        car["metrics"] = metrics
        if unsupported:
            car["templateWarnings"] = [
                {
                    "cardesign": item.get("cardesign") or "",
                    "missing": item.get("missing") or [],
                    "message": item.get("message") or SKIN_TEMPLATE_UNSUPPORTED_MESSAGE,
                }
                for item in unsupported
            ]
    analysis["skinTemplateCounts"] = _json_ready(template_inventory.get("counts") or {})
    analysis["skinTemplates"] = _json_ready(filtered_templates)
    if progress:
        progress(
            {
                "phase": "preparing_thumbnails",
                "current": "Preparing Advanced creator thumbnails",
                "index": car_total + template_total,
                "total": combined_total,
                "counts": car_counts,
                "templateCounts": template_inventory.get("counts") or {},
            }
        )
    advanced_catalog = advanced_creator_catalog.build_catalog(
        root=game_root,
        analysis=analysis,
        output_root=get_advanced_creator_preview_assets_root(),
        convert_texture_to_png=convert_texture_to_png,
        skip_reason=generated_thumbnail_template_skip_reason,
        exclude_cardesign_paths=excluded_cardesign_paths,
        include_cardesign_paths=original_cardesign_paths,
    )
    analysis["advancedCreatorCars"] = _json_ready(advanced_catalog.get("creatorCars") or [])
    analysis["advancedCreatorSummary"] = _json_ready(
        {
            key: value
            for key, value in advanced_catalog.items()
            if key != "creatorCars"
        }
    )
    if progress:
        progress(
            {
                "phase": "saving",
                "current": "Saving car analysis cache",
                "index": car_total + template_total,
                "total": combined_total,
                "counts": car_counts,
                "templateCounts": template_inventory.get("counts") or {},
            }
        )
    saved = save_car_livery_compatibility_cache(game_root, analysis)
    if progress:
        progress(
            {
                "phase": "done",
                "current": "Car analysis complete",
                "index": combined_total,
                "total": combined_total,
                "counts": car_counts,
                "templateCounts": template_inventory.get("counts") or {},
            }
        )
    return saved.get("analysis") or analysis


def _app_runtime_cache_root() -> Path:
    module_root = Path(__file__).resolve().parent
    if module_root.name == "_internal":
        return module_root.parent / "_cache"
    return module_root / "_cache"


def _texture_preview_cache_path(texture_path: Path, mips_path: Path, namespace: str = "textures", max_dimension: int = 0) -> Path:
    texture_stat = texture_path.stat()
    mips_stat = mips_path.stat()
    safe_namespace = preview3d.safe_segment(namespace, "textures")
    key_data = {
        "cacheVersion": 2,
        "texture": str(texture_path.resolve()).lower(),
        "textureSize": int(texture_stat.st_size),
        "textureMtimeNs": int(texture_stat.st_mtime_ns),
        "mips": str(mips_path.resolve()).lower(),
        "mipsSize": int(mips_stat.st_size),
        "mipsMtimeNs": int(mips_stat.st_mtime_ns),
        "maxDimension": int(max_dimension or 0),
    }
    digest = hashlib.sha1(json.dumps(key_data, sort_keys=True).encode("utf-8")).hexdigest()[:24]
    return _app_runtime_cache_root() / "preview3d" / safe_namespace / f"{texture_path.stem}_{digest}.png"


def _resize_png_in_place(png_path: Path, max_dimension: int) -> None:
    if max_dimension <= 0:
        return
    from PIL import Image

    image = Image.open(png_path)
    image.load()
    width = int(image.width)
    height = int(image.height)
    longest = max(width, height)
    if longest <= max_dimension:
        return
    scale = float(max_dimension) / float(longest)
    next_size = (max(1, int(round(width * scale))), max(1, int(round(height * scale))))
    resample = getattr(Image, "Resampling", Image).LANCZOS
    resized = image.resize(next_size, resample)
    resized.save(png_path, format="PNG")


def _read_png_as_data_url(png_path: Path, source_path: Path, cached: bool = False) -> dict[str, Any]:
    from PIL import Image

    image = Image.open(png_path)
    image.load()
    if image.mode not in {"RGB", "RGBA"}:
        image = image.convert("RGBA")
    buffer = io.BytesIO()
    image.save(buffer, format="PNG")
    encoded = base64.b64encode(buffer.getvalue()).decode("ascii")
    return {
        "path": str(source_path),
        "width": int(image.width),
        "height": int(image.height),
        "mime": "image/png",
        "cached": bool(cached),
        "cachePath": str(png_path) if cached else "",
        "data_url": f"data:image/png;base64,{encoded}",
    }


def _image_to_png_data_url(image_path: str | Path, cache_namespace: str = "") -> dict[str, Any]:
    from PIL import Image

    path = Path(image_path)
    suffix = path.suffix.lower()
    if suffix == ".texture":
        mips = path.with_suffix(".texturemips")
        if not mips.exists():
            raise FileNotFoundError(f"Expected .texturemips beside texture preview source: {mips}")
        if _is_ace_savedcar_raw_thumbnail(path, mips):
            return _ace_savedcar_raw_thumbnail_data_url(path)
        namespace = str(cache_namespace or "").strip()
        max_dimension = 1024 if namespace == "rims" else 0
        cache_path = _texture_preview_cache_path(path, mips, namespace, max_dimension=max_dimension) if namespace else None
        if cache_path and cache_path.exists():
            return _read_png_as_data_url(cache_path, path, cached=True)
        with tempfile.TemporaryDirectory(prefix="liverylab_texture_preview_") as tmp:
            tmp_dir = Path(tmp)
            dds_path = tmp_dir / f"{path.stem}.dds"
            png_path = cache_path or (tmp_dir / f"{path.stem}.png")
            convert_to_dds(path, mips, dds_path, "auto")
            convert_dds_to_png(dds_path, png_path)
            if cache_path and max_dimension:
                _resize_png_in_place(png_path, max_dimension)
            return _read_png_as_data_url(png_path, path, cached=bool(cache_path))
    if suffix == ".texturemips":
        raise ValueError("Open the image file itself, not the .texturemips companion.")
    image = Image.open(path)
    image.load()
    if image.mode not in {"RGB", "RGBA"}:
        image = image.convert("RGBA")
    buffer = io.BytesIO()
    image.save(buffer, format="PNG")
    encoded = base64.b64encode(buffer.getvalue()).decode("ascii")
    return {
        "path": str(path),
        "width": int(image.width),
        "height": int(image.height),
        "mime": "image/png",
        "data_url": f"data:image/png;base64,{encoded}",
    }


def file_to_data_url(file_path: str | Path, mime: str = "application/octet-stream", max_bytes: int = 80 * 1024 * 1024) -> dict[str, Any]:
    path = Path(file_path)
    if not path.is_file():
        raise FileNotFoundError(f"File not found: {path}")
    size = path.stat().st_size
    if size > max_bytes:
        raise ValueError(f"File is too large for preview loading: {size:,} bytes")
    encoded = base64.b64encode(path.read_bytes()).decode("ascii")
    return {
        "path": str(path),
        "size": int(size),
        "mime": mime,
        "data_url": f"data:{mime};base64,{encoded}",
    }


def save_data_url_png(out_path: str | Path, data_url: str) -> Path | None:
    prefix = "data:image/png;base64,"
    if not str(data_url).startswith(prefix):
        raise ValueError("Expected PNG data URL")
    raw = base64.b64decode(str(data_url)[len(prefix):])
    return safe_write_bytes(Path(out_path), raw)


def export_numberplate_generator_pngs(
    out_dir: str | Path,
    template_name: str,
    surfaces: list[dict[str, Any]] | tuple[dict[str, Any], ...],
    root: str | Path | None = None,
) -> dict[str, Any]:
    target_root = Path(out_dir).expanduser().resolve()
    root_text = str(root or "").strip() or get_default_root_folder()
    if root_text:
        game_root = normalize_game_root(root_text)
        guarded_roots = [game_root / "content", game_root / "system"]
        if any(_path_is_within(target_root, guarded) for guarded in guarded_roots):
            raise ValueError("Choose a Numberplate PNG export folder outside AC EVO content/system game files.")
    clean_template = sanitize_slug(template_name or "numberplate") or "numberplate"
    written: list[dict[str, Any]] = []
    target_root.mkdir(parents=True, exist_ok=True)
    for row in surfaces or []:
        if not isinstance(row, dict):
            continue
        data_url = str(row.get("dataUrl") or row.get("data_url") or "").strip()
        if not data_url:
            continue
        surface_key = sanitize_slug(str(row.get("surface") or row.get("key") or "plate")) or "plate"
        filename = str(row.get("filename") or f"{clean_template}_{surface_key}.png").strip()
        filename = re.sub(r"[^a-zA-Z0-9_.-]+", "_", Path(filename).name).strip("._-")
        if not filename:
            filename = f"{clean_template}_{surface_key}.png"
        if not filename.lower().endswith(".png"):
            filename = f"{filename}.png"
        target = (target_root / filename).resolve()
        try:
            target.relative_to(target_root)
        except ValueError as exc:
            raise ValueError("Invalid Numberplate PNG export filename") from exc
        backup = save_data_url_png(target, data_url)
        written.append({
            "surface": surface_key,
            "path": str(target),
            "backup": str(backup) if backup else "",
        })
    if not written:
        raise ValueError("No Numberplate PNG data was provided for export.")
    return {
        "outDir": str(target_root),
        "templateName": clean_template,
        "written": written,
        "writtenCount": len(written),
    }

# -----------------------------------------------------------------------------
# Skin creator
# -----------------------------------------------------------------------------

@dataclass
class DesignInfo:
    display_name: str | None
    design_path_from_cardesign: str | None
    rim_path: str | None
    ext_skin_material_path: str | None
    car_content_path: str | None
    source_skin_folder_name: str | None
    source_material_filename: str | None


def norm_slashes(value: str) -> str:
    return value.replace("/", "\\")


def sanitize_slug(text: str) -> str:
    text = text.strip().lower()
    text = text.replace("Ã¤", "ae").replace("Ã¶", "oe").replace("Ã¼", "ue").replace("ÃŸ", "ss")
    text = re.sub(r"[^a-z0-9_-]+", "_", text)
    text = re.sub(r"_+", "_", text).strip("_")
    return text or "custom_skin"


def split_content_path(path: str) -> list[str]:
    return norm_slashes(path).split("\\")


def extract_cardesign_info(cardesign_data: bytes) -> tuple[str | None, str | None]:
    design_path = None; rim_path = None
    for f in parse_fields(cardesign_data):
        if f.wire == 2:
            text = is_printable_utf8(f.value)
            if f.number == 1:
                design_path = text
            elif f.number == 2:
                rim_path = text
    return design_path, rim_path


def extract_design_info(cardesign_data: bytes, design_data: bytes) -> DesignInfo:
    design_path, rim_path = extract_cardesign_info(cardesign_data)
    display_name = None
    ext_material = None
    for f in parse_fields(design_data):
        if f.number == 2 and f.wire == 2:
            display_name = is_printable_utf8(f.value)
        elif f.number == 3 and f.wire == 2:
            try:
                slot = parse_fields(f.value)
            except Exception:
                continue
            slot_id = None; slot_material = None
            for sf in slot:
                if sf.wire == 2:
                    text = is_printable_utf8(sf.value)
                    if sf.number == 1:
                        slot_id = text
                    elif sf.number == 3:
                        slot_material = text
            if slot_id == "EXT_SKIN" and slot_material:
                ext_material = slot_material
    car_content_path = None; skin_folder_name = None; mat_filename = None
    if ext_material:
        parts = split_content_path(ext_material)
        if len(parts) >= 3 and parts[0].lower() == "content" and parts[1].lower() == "cars":
            car_content_path = "\\".join(parts[:3])
        if "skins" in parts:
            si = parts.index("skins")
            car_content_path = "\\".join(parts[:si])
            if len(parts) > si + 1: skin_folder_name = parts[si + 1]
            if len(parts) > si + 2: mat_filename = parts[si + 2]
        elif parts:
            mat_filename = parts[-1]
    return DesignInfo(display_name, design_path, rim_path, ext_material, car_content_path, skin_folder_name, mat_filename)


def resolve_template_design_path(root: Path, cardesign_path: Path, design_path: Path | None = None) -> Path:
    """Resolve the design belonging to a selected .cardesign.

    The .cardesign is the authoritative template selection in the web workflow.
    This avoids stale design input values causing car-path inference failures.
    """
    cardesign_path = Path(cardesign_path)
    if not cardesign_path.exists() or not cardesign_path.is_file():
        raise FileNotFoundError(f"Template .cardesign not found: {cardesign_path}")
    design_rel, _rim_rel = extract_cardesign_info(cardesign_path.read_bytes())
    cardesign_design: Path | None = None
    if design_rel:
        cardesign_design = resolve_content_path(root, design_rel)
        if not cardesign_design.exists():
            fallback = cardesign_path.parent / Path(design_rel.replace("\\", "/")).name
            if fallback.exists():
                cardesign_design = fallback
    provided = Path(design_path) if design_path else None
    if cardesign_design and cardesign_design.exists() and cardesign_design.is_file():
        return cardesign_design
    if provided and provided.exists() and provided.is_file():
        return provided
    raise FileNotFoundError("Could not resolve .design from the selected .cardesign")


def extract_design_slot_materials(design_data: bytes) -> dict[str, list[str]]:
    """Return material references grouped by design slot id."""
    slots: dict[str, list[str]] = {}
    for f in parse_fields(design_data):
        if f.number != 3 or f.wire != 2:
            continue
        try:
            slot_fields = parse_fields(bytes(f.value))
        except Exception:
            continue
        slot_id = None
        slot_material = None
        for sf in slot_fields:
            if sf.wire != 2:
                continue
            text = is_printable_utf8(sf.value)
            if sf.number == 1:
                slot_id = text
            elif sf.number == 3:
                slot_material = text
        if slot_id and slot_material:
            slots.setdefault(slot_id, []).append(slot_material)
    return slots


def _retarget_material_from_source_skin(material_rel: str | None, car_content_path: str | None, source_skin_folder: str | None, new_skin_folder: str | None) -> str | None:
    if not material_rel or not car_content_path or not source_skin_folder or not new_skin_folder:
        return None
    normalized = norm_slashes(material_rel)
    source_prefix = norm_slashes(f"{car_content_path}\\skins\\{source_skin_folder}\\")
    if not normalized.lower().startswith(source_prefix.lower()):
        return None
    return norm_slashes(f"{car_content_path}\\skins\\{new_skin_folder}\\{normalized[len(source_prefix):]}")


def collect_proto_strings(raw: bytes, prefix: str = "", depth: int = 0) -> list[tuple[str, str]]:
    """Collect printable UTF-8 strings from protobuf-like nested fields."""
    if depth > 8:
        return []
    try:
        fields = parse_fields(raw)
    except Exception:
        return []
    found: list[tuple[str, str]] = []
    for idx, f in enumerate(fields, start=1):
        path = f"{prefix}.{f.number}" if prefix else str(f.number)
        if f.wire == 2 and isinstance(f.value, (bytes, bytearray)):
            text = is_printable_utf8(f.value)
            if text is not None:
                found.append((path, text))
            else:
                found.extend(collect_proto_strings(bytes(f.value), path, depth + 1))
    return found


def extract_cardesign_details(cardesign_data: bytes, root: Path | None = None) -> dict[str, Any]:
    design_path, rim_path = extract_cardesign_info(cardesign_data)
    details: dict[str, Any] = {
        "design_path": design_path,
        "rim_path": rim_path,
        "all_strings": collect_proto_strings(cardesign_data),
        "design_local": None,
        "rim_local": None,
    }
    if root is not None:
        if design_path:
            details["design_local"] = str(resolve_content_path(root, design_path))
        if rim_path:
            details["rim_local"] = str(resolve_content_path(root, rim_path))
    return details


def extract_design_details(design_data: bytes, root: Path | None = None) -> dict[str, Any]:
    fields = parse_fields(design_data)
    result: dict[str, Any] = {"display_name": None, "manufacturer": None, "slots": [], "all_strings": collect_proto_strings(design_data)}
    top_strings: list[tuple[int, str]] = []
    for f in fields:
        if f.wire == 2 and isinstance(f.value, (bytes, bytearray)):
            text = is_printable_utf8(f.value)
            if text is not None:
                top_strings.append((f.number, text))
                if f.number == 2:
                    result["display_name"] = text
                elif f.number == 102:
                    result["manufacturer"] = text
            elif f.number == 3:
                try:
                    slot_fields = parse_fields(bytes(f.value))
                except Exception:
                    continue
                slot: dict[str, Any] = {
                    "slot_id": None,
                    "slot_name": None,
                    "material": None,
                    "color_table": None,
                    "colors": [],
                    "option_refs": [],
                    "extra_strings": [],
                    "material_local": None,
                    "color_table_local": None,
                }
                for sf in slot_fields:
                    if sf.wire != 2 or not isinstance(sf.value, (bytes, bytearray)):
                        continue
                    stext = is_printable_utf8(sf.value)
                    if stext is not None:
                        if sf.number == 1:
                            slot["slot_id"] = stext
                        elif sf.number == 2:
                            slot["slot_name"] = stext
                        elif sf.number == 3:
                            slot["material"] = stext
                        elif sf.number == 4:
                            slot["color_table"] = stext
                        elif sf.number == 5:
                            slot["colors"].append(stext)
                        else:
                            slot["extra_strings"].append((str(sf.number), stext))
                    else:
                        nested_strings = collect_proto_strings(bytes(sf.value), str(sf.number))
                        for path, value in nested_strings:
                            # Common for glass/customization choices: paths to *.oemsinglelayercolor etc.
                            if "content\\" in value or "content/" in value or ".texture" in value or ".material" in value or ".oem" in value or ".table" in value:
                                slot["option_refs"].append(value)
                            else:
                                slot["extra_strings"].append((path, value))
                if root is not None:
                    if slot.get("material"):
                        slot["material_local"] = str(resolve_content_path(root, str(slot["material"])))
                    if slot.get("color_table"):
                        slot["color_table_local"] = str(resolve_content_path(root, str(slot["color_table"])))
                result["slots"].append(slot)
    result["top_strings"] = top_strings
    return result


def design_cardesign_report(cardesign_path: Path | None, design_path: Path | None, root: Path | None = None) -> str:
    lines: list[str] = []
    if cardesign_path and cardesign_path.exists():
        cd = extract_cardesign_details(cardesign_path.read_bytes(), root=root)
        lines += [f"Cardesign: {cardesign_path}", f"Design: {cd.get('design_path')}", f"Compatible Rim: {cd.get('rim_path')}"]
        if cd.get("design_local"):
            lines.append(f"Design local: {cd.get('design_local')}")
        if cd.get("rim_local"):
            lines.append(f"Rim local: {cd.get('rim_local')}")
        lines.append("")
    if design_path and design_path.exists():
        dd = extract_design_details(design_path.read_bytes(), root=root)
        lines += [f"Design file: {design_path}", f"Display name: {dd.get('display_name')}", f"Manufacturer: {dd.get('manufacturer')}", f"Slots: {len(dd.get('slots', []))}", ""]
        for idx, slot in enumerate(dd.get("slots", []), start=1):
            lines += [f"Slot {idx}: {slot.get('slot_id')} / {slot.get('slot_name')}", f"  Material: {slot.get('material')}"]
            if slot.get("material_local"):
                lines.append(f"  Material local: {slot.get('material_local')}")
            if slot.get("color_table"):
                lines.append(f"  Color table: {slot.get('color_table')}")
            colors = slot.get("colors") or []
            if colors:
                lines.append(f"  Colors: {', '.join(colors)}")
            opts = slot.get("option_refs") or []
            if opts:
                lines.append("  Options:")
                for opt in opts:
                    lines.append(f"    - {opt}")
            lines.append("")
    return "\n".join(lines).strip() + "\n"


def _inspect_readable_proto_file(path: Path, root: str | Path | None = None, min_raw_len: int = 3) -> dict[str, Any]:
    data = path.read_bytes()
    tree, proto_strings = _actor_walk_records(data)
    raw_strings = _actor_scan_ascii_strings(data, min_len=min_raw_len)
    seen = {(s["offset"], s["length"], s["text"]) for s in proto_strings}
    extra_raw_strings = [
        {**s, "category": _actor_category_for_text(s["text"]), "source": "raw_scan"}
        for s in raw_strings
        if (s["offset"], s["length"], s["text"]) not in seen
    ]
    all_strings = sorted(proto_strings + extra_raw_strings, key=lambda row: (int(row["offset"]), str(row["text"])))
    root_path = Path(root) if root else None
    categories: dict[str, list[dict[str, Any]]] = {}
    references: list[dict[str, Any]] = []
    seen_refs: set[tuple[str, int]] = set()
    for row in all_strings:
        category = str(row.get("category") or "label")
        categories.setdefault(category, []).append(row)
        text = str(row.get("text") or "")
        for rel_start, ref_text in _extract_content_references(text):
            ref_offset = int(row.get("offset") or 0) + rel_start
            key = (ref_text.lower(), ref_offset)
            if key in seen_refs:
                continue
            seen_refs.add(key)
            ref_category = _actor_category_for_text(ref_text)
            ref: dict[str, Any] = {
                "category": ref_category,
                "path": ref_text,
                "offset": ref_offset,
                "offset_hex": f"0x{ref_offset:X}",
                "field_path": row.get("path"),
                "exists": None,
                "resolved_path": None,
            }
            if root_path is not None:
                resolved = resolve_content_path(root_path, ref_text)
                ref["resolved_path"] = str(resolved)
                ref["exists"] = resolved.exists()
            references.append(ref)
    references.sort(key=lambda row: (int(row.get("offset") or 0), str(row.get("path") or "")))
    return {
        "file": str(path),
        "size_bytes": len(data),
        "strings": all_strings,
        "categories": categories,
        "references": references,
        "field_tree": tree,
    }


def inspect_cardesign_readable(cardesign_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    path = Path(cardesign_path)
    result = _inspect_readable_proto_file(path, root=root)
    details = extract_cardesign_details(path.read_bytes(), root=Path(root) if root else None)
    result.update(
        {
            "design_path": details.get("design_path"),
            "rim_path": details.get("rim_path"),
            "design_local": details.get("design_local"),
            "rim_local": details.get("rim_local"),
        }
    )
    return result


def inspect_design_readable(design_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    path = Path(design_path)
    result = _inspect_readable_proto_file(path, root=root)
    details = extract_design_details(path.read_bytes(), root=Path(root) if root else None)
    result.update(
        {
            "display_name": details.get("display_name"),
            "manufacturer": details.get("manufacturer"),
            "slots": details.get("slots", []),
            "top_strings": details.get("top_strings", []),
        }
    )
    return result


def _readable_common_lines(result: dict[str, Any]) -> list[str]:
    lines = [
        f"Readable strings: {len(result.get('strings', []))}",
        f"References: {len(result.get('references', []))}",
        "",
        "Readable data by category:",
    ]
    categories = result.get("categories", {})
    if isinstance(categories, dict):
        for category in sorted(categories):
            rows = categories.get(category) or []
            lines.append(f"  {category}: {len(rows)}")
    lines.extend(["", "References:"])
    for ref in result.get("references", []):
        exists = ref.get("exists")
        exists_text = ""
        if exists is True:
            exists_text = " [found]"
        elif exists is False:
            exists_text = " [missing]"
        field = f" field {ref.get('field_path')}" if ref.get("field_path") else ""
        lines.append(f"{ref.get('offset_hex')} [{ref.get('category')}]{field}: {ref.get('path')}{exists_text}")
    lines.extend(["", "Strings:"])
    for row in result.get("strings", []):
        text = str(row.get("text", "")).replace("\r", "\\r").replace("\n", "\\n")
        field = f" field {row.get('path')}" if row.get("path") else ""
        lines.append(f"{row.get('offset_hex')} [{row.get('category')}]{field}: {text}")
    return lines


def cardesign_readable_summary(result: dict[str, Any]) -> str:
    lines = [
        f"Cardesign: {Path(str(result.get('file'))).name}",
        f"Size: {int(result.get('size_bytes') or 0):,} bytes",
        f"Design: {result.get('design_path') or '-'}",
        f"Compatible Rim: {result.get('rim_path') or '-'}",
    ]
    if result.get("design_local"):
        lines.append(f"Design local: {result.get('design_local')}")
    if result.get("rim_local"):
        lines.append(f"Rim local: {result.get('rim_local')}")
    lines.append("")
    lines.extend(_readable_common_lines(result))
    return "\n".join(lines)


def design_readable_summary(result: dict[str, Any]) -> str:
    lines = [
        f"Design: {Path(str(result.get('file'))).name}",
        f"Size: {int(result.get('size_bytes') or 0):,} bytes",
        f"Display name: {result.get('display_name') or '-'}",
        f"Manufacturer: {result.get('manufacturer') or '-'}",
        f"Slots: {len(result.get('slots', []))}",
        "",
        "Slots:",
    ]
    for idx, slot in enumerate(result.get("slots", []), start=1):
        lines.append(f"  Slot {idx}: {slot.get('slot_id') or '-'} / {slot.get('slot_name') or '-'}")
        if slot.get("material"):
            lines.append(f"    Material: {slot.get('material')}")
        if slot.get("material_local"):
            lines.append(f"    Material local: {slot.get('material_local')}")
        if slot.get("color_table"):
            lines.append(f"    Color table: {slot.get('color_table')}")
        colors = slot.get("colors") or []
        if colors:
            lines.append(f"    Colors: {', '.join(str(c) for c in colors)}")
        opts = slot.get("option_refs") or []
        for opt in opts:
            lines.append(f"    Option: {opt}")
    lines.append("")
    lines.extend(_readable_common_lines(result))
    return "\n".join(lines)


def inspect_cardescription_readable(cardescription_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    path = Path(cardescription_path)
    result = _inspect_readable_proto_file(path, root=root)
    description_text = None
    for field in parse_fields(path.read_bytes()):
        if field.number == 100 and field.wire == 2:
            description_text = is_printable_utf8(field.value)
            if description_text is not None:
                break
    result.update({"description_text": description_text})
    return result


def cardescription_readable_summary(result: dict[str, Any]) -> str:
    lines = [
        f"Car Description: {Path(str(result.get('file'))).name}",
        f"Size: {int(result.get('size_bytes') or 0):,} bytes",
        f"Description: {result.get('description_text') or '-'}",
        "",
    ]
    lines.extend(_readable_common_lines(result))
    return "\n".join(lines)


def make_cardesign(template_data: bytes, new_design_path: str) -> bytes:
    new_fields = []
    for f in parse_fields(template_data):
        if f.number == 1 and f.wire == 2:
            new_fields.append(ProtoField(f.number, f.wire, norm_slashes(new_design_path).encode("utf-8")))
        else:
            new_fields.append(f)
    return build_fields(new_fields)


def make_design(template_data: bytes, new_display_name: str, old_material_path: str, new_material_path: str, material_replacements: dict[str, str] | None = None) -> bytes:
    new_fields = []
    replacements = {_normalize_exact_text(old_material_path): norm_slashes(new_material_path)}
    for old, new in (material_replacements or {}).items():
        replacements[_normalize_exact_text(str(old))] = norm_slashes(str(new))
    for f in parse_fields(template_data):
        if f.number == 2 and f.wire == 2:
            new_fields.append(ProtoField(f.number, f.wire, new_display_name.encode("utf-8")))
        elif f.wire == 2:
            new_fields.append(ProtoField(f.number, f.wire, patch_strings_recursive_exact(f.value, replacements)))
        else:
            new_fields.append(f)
    return build_fields(new_fields)



def edit_cardesign_data(data: bytes, design_path: str | None = None, rim_path: str | None = None) -> bytes:
    """Edit known top-level fields in a .cardesign file."""
    new_fields: list[ProtoField] = []
    for f in parse_fields(data):
        if f.wire == 2 and f.number == 1 and design_path is not None:
            new_fields.append(ProtoField(f.number, f.wire, norm_slashes(design_path).encode("utf-8")))
        elif f.wire == 2 and f.number == 2 and rim_path is not None:
            new_fields.append(ProtoField(f.number, f.wire, norm_slashes(rim_path).encode("utf-8")))
        else:
            new_fields.append(f)
    return build_fields(new_fields)


def edit_design_data(data: bytes, display_name: str | None = None, manufacturer: str | None = None, ext_skin_material: str | None = None, material_replacements: dict[str, str] | None = None) -> bytes:
    """Edit known fields in a .design file and optionally exact material references."""
    normalized_replacements = {_normalize_exact_text(str(old)): norm_slashes(str(new)) for old, new in (material_replacements or {}).items()}
    new_fields: list[ProtoField] = []
    manufacturer_written = False
    for f in parse_fields(data):
        if f.wire == 2 and f.number == 2 and display_name is not None:
            new_fields.append(ProtoField(f.number, f.wire, display_name.encode("utf-8")))
            continue
        if f.wire == 2 and f.number == 102:
            manufacturer_written = True
            new_fields.append(ProtoField(f.number, f.wire, manufacturer.encode("utf-8")) if manufacturer is not None else f)
            continue
        if f.wire == 2 and f.number == 3 and ext_skin_material is not None:
            try:
                slot_fields = parse_fields(bytes(f.value))
                slot_id = None
                for sf in slot_fields:
                    if sf.wire == 2 and sf.number == 1:
                        slot_id = is_printable_utf8(sf.value)
                if slot_id == "EXT_SKIN":
                    patched_slot: list[ProtoField] = []
                    for sf in slot_fields:
                        if sf.wire == 2 and sf.number == 3:
                            patched_slot.append(ProtoField(sf.number, sf.wire, norm_slashes(ext_skin_material).encode("utf-8")))
                        else:
                            patched_slot.append(sf)
                    new_fields.append(ProtoField(f.number, f.wire, build_fields(patched_slot)))
                    continue
            except Exception:
                pass
        if f.wire == 2 and f.number == 3 and normalized_replacements:
            patched = patch_strings_recursive_exact(bytes(f.value), normalized_replacements)
            if patched != bytes(f.value):
                new_fields.append(ProtoField(f.number, f.wire, patched))
                continue
        new_fields.append(f)
    if manufacturer is not None and not manufacturer_written and manufacturer.strip():
        new_fields.append(ProtoField(102, 2, manufacturer.encode("utf-8")))
    return build_fields(new_fields)


def content_to_disk(root: Path, rel: str) -> Path:
    parts = split_content_path(rel)
    if parts and parts[0].lower() == "content" and root.name.lower() == "content":
        return root / Path(*parts[1:])
    return root / Path(*parts)


def resolve_content_path(root: Path, rel: str) -> Path:
    parts = split_content_path(rel)
    candidates = [root / Path(*parts)]
    if parts and parts[0].lower() == "content":
        candidates.append(root / Path(*parts[1:]))
        candidates.append(root.parent / Path(*parts))
    if "skins" in parts:
        si = parts.index("skins")
        candidates.append(root / Path(*parts[si:]))
    for c in candidates:
        if c.exists(): return c
    return candidates[0]


def game_rel_from_disk(root: Path, path: Path | None) -> str | None:
    if path is None:
        return None
    game_root = normalize_game_root(root)
    try:
        rel = path.resolve().relative_to(game_root.resolve())
        return norm_slashes(str(rel))
    except Exception:
        pass
    content_rel = content_rel_from_disk(game_root, path)
    if content_rel:
        return content_rel
    return None


def find_material_file(root: Path, material_rel: str) -> Path | None:
    direct = resolve_content_path(root, material_rel)
    if direct.exists(): return direct
    name = Path(material_rel.replace("\\", "/")).name.lower()
    try:
        for p in root.rglob(name):
            if p.is_file() and p.name.lower() == name:
                return p
    except Exception:
        pass
    return None


def create_skin_local_banner_material(root: Path, source_banner_material_rel: str, car_content_path: str, new_skin_id: str, overwrite: bool = False, writer: GameFileWriter | None = None, output_root: Path | None = None) -> tuple[str | None, list[str]]:
    """Create a skin-local EXT_BANNER material when the template uses a car-level banner material."""
    target_root = output_root or root
    normalized_source = norm_slashes(source_banner_material_rel)
    if f"\\skins\\" in normalized_source.lower():
        return None, []
    if not normalized_source.lower().endswith(".material"):
        return None, []
    source_material = find_material_file(root, normalized_source)
    if source_material is None or not source_material.exists():
        return None, []

    new_skin_rel = norm_slashes(f"{car_content_path}\\skins\\{new_skin_id}")
    source_banner_path = Path(normalized_source.replace("\\", "/"))
    source_banner_name = source_banner_path.name
    if source_banner_path.stem.upper() == "EXT_BANNER":
        source_banner_name = "EXT_BANNER_PRES.material"
    new_banner_rel = norm_slashes(f"{new_skin_rel}\\{source_banner_name}")
    new_banner_disk = content_to_disk(target_root, new_banner_rel)
    if new_banner_disk.exists() and not overwrite:
        return new_banner_rel, []

    texture_replacements: dict[str, str] = {}
    copied: list[str] = []
    material_info = inspect_material(source_material, root=root)
    for slot in material_info.get("texture_slots", []):
        if str(slot.get("name") or "") != "Base_BaseColorMap":
            continue
        texture_rel = slot.get("value")
        if not isinstance(texture_rel, str) or ".texture" not in texture_rel.lower():
            continue
        normalized_texture = norm_slashes(texture_rel)
        parts = split_content_path(normalized_texture)
        if "textures" not in [part.lower() for part in parts] or "banner" not in Path(normalized_texture.replace("\\", "/")).name.lower():
            continue
        source_texture = material_resolve_ref(root, normalized_texture) or resolve_content_path(root, normalized_texture)
        if not source_texture.exists():
            continue
        new_texture_rel = norm_slashes(f"{new_skin_rel}\\{source_texture.name}")
        target_texture = content_to_disk(target_root, new_texture_rel)
        for source_file, target_file in ((source_texture, target_texture), (source_texture.with_suffix(".texturemips"), target_texture.with_suffix(".texturemips"))):
            if not source_file.exists():
                continue
            if target_file.exists() and not overwrite:
                continue
            _copy_game_file(source_file, target_file, writer, label="skin-local banner texture")
            if target_file.suffix.lower() == ".texture":
                localize_texture_descriptor_source(target_file, new_skin_id, writer=writer)
            copied.append(str(target_file))
        texture_replacements[normalized_texture] = new_texture_rel

    new_material_data = patch_strings_recursive_exact(source_material.read_bytes(), {_normalize_exact_text(old): new for old, new in texture_replacements.items()})
    _write_game_bytes(new_banner_disk, new_material_data, writer, label="skin-local banner material")
    copied.append(str(new_banner_disk))
    return new_banner_rel, copied


def _solid_texturemips_for_descriptor_block(texture_path: Path, reference_mips_path: Path, block: bytes) -> bytes:
    desc = parse_texture_descriptor(texture_path)
    block_bytes = infer_block_bytes_from_tiled_size(reference_mips_path.stat().st_size, desc)
    if block_bytes != 8:
        raise RuntimeError(
            f"Solid texturemips generation can only write 8-byte BC maps right now. "
            f"{texture_path.name} appears to use {block_bytes or 'unknown'} bytes per block."
        )
    if len(block) != block_bytes:
        raise RuntimeError(f"Solid block size mismatch for {texture_path.name}: got {len(block)}, expected {block_bytes}")
    size = reference_mips_path.stat().st_size
    if size % len(block) != 0:
        raise RuntimeError(f"Unexpected .texturemips size for {texture_path.name}: {size:,} bytes")
    tile_w = int(desc["tile_w"])
    tile_h = int(desc["tile_h"])
    tile_bw = max(1, (tile_w + 3) // 4)
    tile_bh = max(1, (tile_h + 3) // 4)
    tile_row_bytes = tile_bw * block_bytes
    tile_size = tile_row_bytes * tile_bh
    expected = sum(desc["mip_tile_counts"]) * tile_size
    if size != expected:
        raise RuntimeError(f"Unexpected .texturemips size for {texture_path.name}: {size:,} bytes, expected {expected:,}")
    out = bytearray(size)
    cur_w = int(desc["width"])
    cur_h = int(desc["height"])
    for mip in range(int(desc["mips"])):
        mip_bw = max(1, (cur_w + 3) // 4)
        mip_bh = max(1, (cur_h + 3) // 4)
        tiles_x = (cur_w + tile_w - 1) // tile_w
        first_tile = int(desc["mip_tile_offsets"][mip])
        for by in range(mip_bh):
            ty = by // tile_bh
            local_by = by % tile_bh
            for bx in range(mip_bw):
                tx = bx // tile_bw
                local_bx = bx % tile_bw
                tile_index = first_tile + ty * tiles_x + tx
                offset = tile_index * tile_size + local_by * tile_row_bytes + local_bx * block_bytes
                out[offset:offset + block_bytes] = block
        cur_w = max(1, cur_w // 2)
        cur_h = max(1, cur_h // 2)
    return bytes(out)


def _solid_texturemips_for_descriptor(texture_path: Path, reference_mips_path: Path, color_name: str) -> bytes:
    block = (
        paint_only_livery.solid_bc4_block(color_name)
        if is_ext_skin_bc4_material_map(texture_path)
        else paint_only_livery.solid_bc1_block(color_name)
    )
    return _solid_texturemips_for_descriptor_block(texture_path, reference_mips_path, block)


def _solid_bc4_texturemips_for_descriptor_value(texture_path: Path, reference_mips_path: Path, value: int) -> bytes:
    if not is_ext_skin_bc4_material_map(texture_path):
        raise RuntimeError(f"{texture_path.name} is not an EXT_SKIN BC4 material map")
    return _solid_texturemips_for_descriptor_block(
        texture_path,
        reference_mips_path,
        paint_only_livery.solid_bc4_value_block(value),
    )


def _solid_bc1_texturemips_for_descriptor(texture_path: Path, reference_mips_path: Path, color_name: str) -> bytes:
    return _solid_texturemips_for_descriptor(texture_path, reference_mips_path, color_name)


def _write_black_spat_texturemips(texture_path: Path, writer: GameFileWriter | None = None) -> dict[str, Any]:
    texture_path = Path(texture_path)
    mips_path = texture_path.with_suffix(".texturemips")
    if not texture_path.exists():
        return {"updated": False, "texture": str(texture_path), "reason": "texture missing"}
    if not mips_path.exists():
        return {"updated": False, "texture": str(texture_path), "reason": "texturemips missing"}
    desc = parse_texture_descriptor(texture_path)
    format_name, auto_note = choose_auto_format(texture_path, desc, mips_path.stat().st_size, "SpatMap")
    tmp_dir = create_short_work_temp_dir("sb")
    try:
        try:
            from PIL import Image
        except Exception as exc:
            raise RuntimeError(f"Pillow is required to neutralize EXT_Skin_D: {exc}") from exc
        black_png = tmp_dir / "ext_skin_d_black.png"
        dds_path = tmp_dir / "ext_skin_d_black.dds"
        Image.new("RGBA", (int(desc["width"]), int(desc["height"])), (0, 0, 0, 255)).save(black_png)
        convert_image_to_dds_with_texconv(
            black_png,
            dds_path,
            format_name,
            None,
            int(desc["mips"]),
            (int(desc["width"]), int(desc["height"])),
        )
        texturemips_data = texturemips_bytes_from_dds(texture_path, dds_path)
    finally:
        cleanup_short_work_temp_dir(tmp_dir)
    if mips_path.read_bytes() == texturemips_data:
        return {
            "updated": False,
            "texture": str(texture_path),
            "texturemips": str(mips_path),
            "reason": "current",
            "format": format_name,
            "note": auto_note,
        }
    backup = _write_game_bytes(mips_path, texturemips_data, writer, label="skin-local neutral EXT_Skin_D", backup=False)
    return {
        "updated": True,
        "texture": str(texture_path),
        "texturemips": str(mips_path),
        "backup": str(backup) if backup else "",
        "format": format_name,
        "note": auto_note,
        "bytes": len(texturemips_data),
    }


def neutralize_skin_spat_map(new_skin_folder_disk: Path, writer: GameFileWriter | None = None) -> dict[str, Any]:
    texture = _find_texture_by_stem(new_skin_folder_disk, "ext_skin_d")
    if texture is None:
        return {"updated": False, "reason": "ext_skin_d texture missing", "skinFolder": str(new_skin_folder_disk)}
    return _write_black_spat_texturemips(texture, writer=writer)


def _material_float_parameter_value(value: float) -> bytes:
    return build_fields([ProtoField(1, 5, struct.pack("<f", float(value)))])


def _set_material_float_parameters(data: bytes, names: set[str], value: float) -> bytes:
    replacement = _material_float_parameter_value(value)
    changed = False
    outer_fields: list[ProtoField] = []
    for field in parse_fields(data):
        if field.number == 4 and field.wire == 2 and isinstance(field.value, bytes):
            entry_fields: list[ProtoField] = []
            entry_name = None
            entry_changed = False
            for entry_field in parse_fields(bytes(field.value)):
                if entry_field.number == 1 and entry_field.wire == 2 and isinstance(entry_field.value, bytes):
                    entry_name = is_printable_utf8(entry_field.value)
                    entry_fields.append(entry_field)
                    continue
                if entry_field.number == 2 and entry_field.wire == 2 and entry_name in names:
                    if bytes(entry_field.value) != replacement:
                        entry_fields.append(ProtoField(entry_field.number, entry_field.wire, replacement))
                        entry_changed = True
                    else:
                        entry_fields.append(entry_field)
                    continue
                entry_fields.append(entry_field)
            if entry_changed:
                outer_fields.append(ProtoField(field.number, field.wire, build_fields(entry_fields)))
                changed = True
                continue
        outer_fields.append(field)
    return build_fields(outer_fields) if changed else data


def _paint_only_template_texture(root: Path, car_content_path: str, texture_key: str) -> Path:
    template_ref = paint_only_livery.TEMPLATE_TEXTURE_RELS[texture_key]
    candidate = resolve_content_path(root, template_ref)
    if candidate.exists():
        return candidate
    raise FileNotFoundError(f"Paint-only livery texture template not found: {template_ref}")


def _source_skin_livery_texture(root: Path, info: DesignInfo, texture_key: str) -> Path | None:
    if not info.car_content_path or not info.source_skin_folder_name:
        return None
    names = [paint_only_livery.TEXTURE_FILENAMES.get(texture_key, "")]
    for name in names:
        if not name:
            continue
        candidate = resolve_content_path(root, f"{info.car_content_path}\\skins\\{info.source_skin_folder_name}\\{name}")
        if candidate.exists() and candidate.with_suffix(".texturemips").exists():
            return candidate
    return None


def _paint_only_livery_source_texture(root: Path, info: DesignInfo, texture_key: str) -> Path:
    source_skin_texture = _source_skin_livery_texture(root, info, texture_key)
    if source_skin_texture:
        return source_skin_texture
    return _paint_only_template_texture(root, info.car_content_path or "", texture_key)


def _paint_only_livery_target_ao_ref(root: Path, info: DesignInfo) -> str | None:
    car_prefix = norm_slashes(info.car_content_path or "").lower().rstrip("\\")
    material_ref = norm_slashes(info.ext_skin_material_path or "")
    if material_ref:
        material_path = resolve_content_path(root, material_ref)
        if material_path.exists():
            try:
                material = inspect_material(material_path, root=root)
            except Exception:
                material = {}
            for slot in material.get("texture_slots", []):
                if str(slot.get("name") or "") != paint_only_livery.AO_SLOT:
                    continue
                value = norm_slashes(str(slot.get("value") or "").strip())
                if ".texture" not in value.lower():
                    continue
                if car_prefix and not value.lower().startswith(car_prefix + "\\"):
                    continue
                resolved_text = str(slot.get("resolved_path") or "").strip()
                resolved = Path(resolved_text) if resolved_text else resolve_content_path(root, value)
                if resolved.exists() and resolved.suffix.lower() == ".texture":
                    return value
    return paint_only_livery.find_target_ao_ref(
        info.car_content_path,
        lambda ref: resolve_content_path(root, ref).exists(),
    )


def _material_has_basecolor_texture(root: Path, material_ref: str | None) -> bool:
    if not material_ref:
        return False
    material_path = resolve_content_path(root, material_ref)
    if not material_path.exists():
        return False
    try:
        material = inspect_material(material_path, root=root)
    except Exception:
        material = {}
    for slot in material.get("texture_slots", []):
        slot_name = str(slot.get("name") or "").lower()
        value = str(slot.get("value") or "").strip()
        if "basecolor" in slot_name and value:
            return True
    for name in ("EXT_SKIN_C.texture", "ext_skin_c.texture", "skin_c.texture", "livery_c.texture", "body_c.texture"):
        if (material_path.parent / name).exists():
            return True
    return False


def _paint_only_livery_upgrade_available(root: Path, info: DesignInfo) -> bool:
    if not paint_only_livery.is_car_level_ext_skin_material(info.car_content_path, info.ext_skin_material_path):
        return False
    if not resolve_content_path(root, paint_only_livery.TEMPLATE_MATERIAL_REL).exists():
        return False
    if not info.ext_skin_material_path or not resolve_content_path(root, info.ext_skin_material_path).exists():
        return False
    return True


def _skin_local_livery_upgrade_available(root: Path, info: DesignInfo) -> bool:
    if not info.source_skin_folder_name or not info.ext_skin_material_path:
        return False
    if not resolve_content_path(root, paint_only_livery.TEMPLATE_MATERIAL_REL).exists():
        return False
    if not resolve_content_path(root, info.ext_skin_material_path).exists():
        return False
    return not _material_has_basecolor_texture(root, info.ext_skin_material_path)


def _livery_material_upgrade_available(root: Path, info: DesignInfo) -> bool:
    return _paint_only_livery_upgrade_available(root, info) or _skin_local_livery_upgrade_available(root, info)


def create_paint_only_livery_skin_assets(
    root: Path,
    info: DesignInfo,
    new_skin_id: str,
    new_skin_folder_disk: Path,
    new_material_disk: Path,
    overwrite: bool = False,
    writer: GameFileWriter | None = None,
    output_root: Path | None = None,
) -> tuple[list[str], list[str]]:
    if not info.car_content_path or not info.ext_skin_material_path:
        raise RuntimeError("Paint-only livery upgrade needs a car content path and EXT_SKIN material.")
    target_root = output_root or root
    new_skin_folder_disk.mkdir(parents=True, exist_ok=True)
    copied: list[str] = []
    patched: list[str] = []

    template_material = resolve_content_path(root, paint_only_livery.TEMPLATE_MATERIAL_REL)
    if not template_material.exists():
        raise FileNotFoundError(f"Paint-only livery material template not found: {paint_only_livery.TEMPLATE_MATERIAL_REL}")

    for texture_key in paint_only_livery.TEXTURE_FILENAMES:
        source_texture = _paint_only_livery_source_texture(root, info, texture_key)
        source_mips = source_texture.with_suffix(".texturemips")
        if not source_mips.exists():
            raise FileNotFoundError(f"Texture template has no .texturemips beside it: {source_texture}")
        texture_rel = paint_only_livery.local_texture_rel(info.car_content_path, new_skin_id, texture_key)
        target_texture = content_to_disk(target_root, texture_rel)
        target_mips = target_texture.with_suffix(".texturemips")
        for target in (target_texture, target_mips):
            if target.exists() and not overwrite:
                raise FileExistsError(f"Target file exists: {target}")
        _copy_game_file(source_texture, target_texture, writer, label="paint-only livery texture descriptor")
        localize_texture_descriptor_source(target_texture, new_skin_id, writer=writer)
        solid_name = paint_only_livery.default_solid_for_texture(texture_key)
        slot_name = next(
            (slot for slot, key in paint_only_livery.SLOT_TO_TEXTURE_KEY.items() if key == texture_key),
            "",
        )
        solid_mips, _format_name, _note = _neutral_ext_skin_texturemips_for_descriptor(
            target_texture,
            source_mips,
            solid_name,
            slot_name,
        )
        _write_game_bytes(target_mips, solid_mips, writer, label="paint-only livery neutral texture")
        copied.extend([str(target_texture), str(target_mips)])

    if new_material_disk.exists() and not overwrite:
        raise FileExistsError(f"Target material file exists: {new_material_disk}")
    ao_ref = _paint_only_livery_target_ao_ref(root, info)
    template_material_info = inspect_material(template_material, root=root)
    template_slots = {
        str(slot.get("name") or ""): str(slot.get("value") or "")
        for slot in template_material_info.get("texture_slots", [])
        if str(slot.get("name") or "") and str(slot.get("value") or "")
    }
    exact_replacements: dict[str, str] = {}
    if template_material_info.get("material_path"):
        exact_replacements[_normalize_exact_text(str(template_material_info["material_path"]))] = info.ext_skin_material_path
    for slot_name, texture_key in paint_only_livery.SLOT_TO_TEXTURE_KEY.items():
        old_ref = template_slots.get(slot_name) or paint_only_livery.TEMPLATE_TEXTURE_RELS[texture_key]
        exact_replacements[_normalize_exact_text(old_ref)] = paint_only_livery.local_texture_rel(info.car_content_path, new_skin_id, texture_key)
    if ao_ref:
        old_ao_ref = template_slots.get(paint_only_livery.AO_SLOT)
        if old_ao_ref:
            exact_replacements[_normalize_exact_text(old_ao_ref)] = ao_ref
    material_data = patch_strings_recursive_exact(template_material.read_bytes(), exact_replacements)
    if ao_ref:
        material_data = _set_material_texture_slots_nested(material_data, {paint_only_livery.AO_SLOT: ao_ref})
    material_data = _set_material_float_parameters(
        material_data,
        set(paint_only_livery.DISABLED_BASE_MAP_FLAGS),
        0.0,
    )
    material_data = ensure_material_parent_and_texture_overrides(
        material_data,
        info.ext_skin_material_path,
        [],
        paint_only_livery.texture_override_slots(),
    )
    _write_game_bytes(new_material_disk, material_data, writer, label="paint-only livery material")
    copied.append(str(new_material_disk))
    patched.append(str(new_material_disk))
    return copied, patched


def copy_retargeted_skin_local_materials(
    root: Path,
    target_root: Path,
    info: DesignInfo,
    material_replacements: dict[str, str],
    new_material_rel: str,
    overwrite: bool = False,
    writer: GameFileWriter | None = None,
) -> tuple[list[str], list[str]]:
    source_skin_rel = norm_slashes(f"{info.car_content_path}\\skins\\{info.source_skin_folder_name}") if info.car_content_path and info.source_skin_folder_name else ""
    if not source_skin_rel:
        return [], []
    copied: list[str] = []
    patched: list[str] = []
    patch_replacements = {
        norm_slashes(source_skin_rel): norm_slashes(str(Path(*split_content_path(new_material_rel)).parent)),
    }
    patch_replacements.update({norm_slashes(str(old)): norm_slashes(str(new)) for old, new in material_replacements.items()})
    source_main_material = norm_slashes(str(info.ext_skin_material_path or "")).lower()
    target_new_material = norm_slashes(new_material_rel).lower()
    for source_rel, target_rel in material_replacements.items():
        source_norm = norm_slashes(str(source_rel))
        target_norm = norm_slashes(str(target_rel))
        if not source_norm.lower().startswith(source_skin_rel.lower() + "\\"):
            continue
        if not source_norm.lower().endswith(".material") or not target_norm.lower().endswith(".material"):
            continue
        if source_norm.lower() == source_main_material:
            continue
        if target_norm.lower() == target_new_material:
            continue
        source_path = resolve_content_path(root, source_norm)
        if not source_path.exists() or not source_path.is_file():
            continue
        target_path = content_to_disk(target_root, target_norm)
        if target_path.exists() and not overwrite:
            continue
        data = patch_strings_recursive(source_path.read_bytes(), patch_replacements)
        _write_game_bytes(target_path, data, writer, label="retargeted skin-local material", backup=False)
        copied.append(str(target_path))
        if data != source_path.read_bytes():
            patched.append(str(target_path))
    return copied, patched


def repair_missing_skin_local_material_dependencies(entry: dict[str, Any], game_root: Path | None = None) -> dict[str, Any]:
    root = game_root or normalize_game_root(str(entry.get("game_root") or ""))
    local_root = _entry_local_root(entry)
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    source_skin = entry.get("source_skin") if isinstance(entry.get("source_skin"), dict) else {}
    skin_folder_rel = normalize_skin_asset_rel_path(str(files.get("skin_folder") or ""))
    source_skin_rel = normalize_skin_asset_rel_path(str(source_skin.get("skin_folder") or ""))
    if local_root is None or not skin_folder_rel or not source_skin_rel:
        return {"changed": False, "copied": [], "skipped": []}

    missing_refs = _missing_activation_content_references(entry, root)
    material_refs: list[str] = []
    seen: set[str] = set()
    for row in missing_refs:
        ref = normalize_skin_asset_rel_path(str(row.get("path") or ""))
        if not ref.lower().endswith(".material"):
            continue
        if not ref.lower().startswith(skin_folder_rel.lower() + "\\"):
            continue
        key = ref.lower()
        if key in seen:
            continue
        seen.add(key)
        material_refs.append(ref)
    if not material_refs:
        return {"changed": False, "copied": [], "skipped": []}

    writer = GameFileWriter(
        local_root,
        "repair local skin material dependencies",
        [local_root / Path(*split_content_path(skin_folder_rel))],
    )
    patch_replacements = {
        norm_slashes(source_skin_rel): norm_slashes(skin_folder_rel),
    }
    source_material = normalize_skin_asset_rel_path(str(source_skin.get("material") or ""))
    target_material = normalize_skin_asset_rel_path(str(files.get("material") or ""))
    if source_material and target_material:
        patch_replacements[source_material] = target_material

    copied: list[str] = []
    skipped: list[dict[str, str]] = []
    for target_rel in material_refs:
        suffix = norm_slashes(target_rel)[len(norm_slashes(skin_folder_rel)):].lstrip("\\/")
        source_rel = norm_slashes(f"{source_skin_rel}\\{suffix}")
        source_path = resolve_content_path(root, source_rel)
        target_path = local_root / Path(*split_content_path(target_rel))
        if target_path.exists():
            continue
        if not source_path.exists() or not source_path.is_file():
            skipped.append({"target": target_rel, "source": source_rel, "reason": "source missing"})
            continue
        data = patch_strings_recursive(source_path.read_bytes(), patch_replacements)
        _write_game_bytes(target_path, data, writer, label="repair missing skin-local material", backup=False)
        copied.append(str(target_path))

    return {
        "changed": bool(copied),
        "copied": copied,
        "skipped": skipped,
        "writer": writer.to_dict(),
    }


def copytree_or_copyfiles(src: Path, dst: Path, overwrite: bool = False, writer: GameFileWriter | None = None) -> list[str]:
    if not src.exists() or not src.is_dir():
        raise FileNotFoundError(f"Source skin folder not found: {src}")
    if dst.exists() and any(dst.iterdir()) and not overwrite:
        raise FileExistsError(f"Target skin folder already exists and is not empty: {dst}")
    dst.mkdir(parents=True, exist_ok=True)
    copied = []
    for item in src.rglob("*"):
        rel = item.relative_to(src)
        if any(part.lower() == "_acevo_backups" for part in rel.parts):
            continue
        target = dst / rel
        if item.is_dir():
            target.mkdir(parents=True, exist_ok=True)
        elif item.is_file():
            if target.exists() and not overwrite:
                raise FileExistsError(f"Target file exists: {target}")
            _copy_game_file(item, target, writer, label="skin source file")
            copied.append(str(target))
    return copied


def patch_copied_files_for_new_skin(folder: Path, replacements: dict[str, str], writer: GameFileWriter | None = None) -> list[str]:
    patched = []
    for p in folder.rglob("*"):
        if not p.is_file() or p.suffix.lower() not in {".material", ".texture", ".design", ".cardesign"}:
            continue
        data = p.read_bytes()
        new_data = patch_strings_recursive(data, replacements)
        if new_data != data:
            _write_game_bytes(p, new_data, writer, label="copied skin reference patch", backup=False)
            patched.append(str(p))
    return patched


def _neutral_solid_for_ext_skin_texture(stem: str) -> str:
    normalized = str(stem or "").strip().lower()
    if normalized == "ext_skin_c":
        return "white"
    if normalized in {"ext_skin_r"}:
        return "roughness_neutral"
    if normalized in {"ext_skin_cr", "ext_skin_ao", "ext_skin_drift_ao"}:
        return "white"
    return "black"


def _solid_rgba_for_ext_skin_color(color_name: str) -> tuple[int, int, int, int]:
    normalized = str(color_name or "").strip().lower()
    if normalized == "white":
        return (255, 255, 255, 255)
    if normalized == "roughness_neutral":
        return (190, 190, 190, 255)
    return (0, 0, 0, 255)


def _neutral_ext_skin_texturemips_for_descriptor(
    texture_path: Path,
    reference_mips_path: Path,
    color_name: str,
    slot_name: str,
) -> tuple[bytes, str, str]:
    desc = parse_texture_descriptor(texture_path)
    block_bytes = infer_block_bytes_from_tiled_size(reference_mips_path.stat().st_size, desc)
    if block_bytes == 16:
        return _solid_texturemips_from_rgba(
            texture_path,
            reference_mips_path,
            slot_name,
            _solid_rgba_for_ext_skin_color(color_name),
        )
    return _solid_bc1_texturemips_for_descriptor(texture_path, reference_mips_path, color_name), "", ""


READY_TEMPLATE_NEUTRAL_MATERIAL_MAP_VALUES = {
    "metalness": 0,
    "roughness": 0,
    "clearcoat": 128,
    "clearcoat_roughness": 128,
}


def neutralize_ready_template_material_textures(new_skin_folder_disk: Path, writer: GameFileWriter | None = None) -> dict[str, Any]:
    items: list[dict[str, Any]] = []
    updated = 0
    skipped = 0
    missing = 0

    base = _find_texture_by_stem(new_skin_folder_disk, "ext_skin_c")
    if base is None:
        items.append({"stem": "ext_skin_c", "status": "missing", "reason": "texture missing"})
        missing += 1
    else:
        mips = base.with_suffix(".texturemips")
        if not mips.exists():
            items.append({"stem": "ext_skin_c", "status": "missing", "reason": "texturemips missing", "texture": str(base)})
            missing += 1
        else:
            try:
                neutral_mips, format_name, note = _neutral_ext_skin_texturemips_for_descriptor(base, mips, "white", "Base_BaseColorMap")
                if mips.read_bytes() == neutral_mips:
                    item = {"stem": "ext_skin_c", "status": "current", "texture": str(base), "texturemips": str(mips)}
                    if format_name:
                        item.update({"format": format_name, "note": note})
                    items.append(item)
                    skipped += 1
                else:
                    _write_game_bytes(mips, neutral_mips, writer, label="ready template neutral base texture", backup=False)
                    item = {"stem": "ext_skin_c", "status": "updated", "texture": str(base), "texturemips": str(mips), "bytes": len(neutral_mips)}
                    if format_name:
                        item.update({"format": format_name, "note": note})
                    items.append(item)
                    updated += 1
            except Exception as exc:
                items.append({"stem": "ext_skin_c", "status": "skipped", "reason": str(exc), "texture": str(base), "texturemips": str(mips)})
                skipped += 1

    spat = _find_texture_by_stem(new_skin_folder_disk, "ext_skin_d")
    if spat is None:
        items.append({"stem": "ext_skin_d", "status": "missing", "reason": "texture missing"})
        missing += 1
    else:
        try:
            result = _write_black_spat_texturemips(spat, writer=writer)
            status = "updated" if result.get("updated") else "current"
            items.append({"stem": "ext_skin_d", "status": status, **result})
            if result.get("updated"):
                updated += 1
            else:
                skipped += 1
        except Exception as exc:
            items.append({"stem": "ext_skin_d", "status": "skipped", "reason": str(exc), "texture": str(spat)})
            skipped += 1

    for texture_key, byte_value in READY_TEMPLATE_NEUTRAL_MATERIAL_MAP_VALUES.items():
        filename = paint_only_livery.TEXTURE_FILENAMES.get(texture_key, "")
        stem = Path(filename).stem
        texture = _find_texture_by_stem(new_skin_folder_disk, stem)
        if texture is None:
            items.append({"stem": stem, "status": "missing", "reason": "texture missing"})
            missing += 1
            continue
        mips = texture.with_suffix(".texturemips")
        if not mips.exists():
            items.append({"stem": stem, "status": "missing", "reason": "texturemips missing", "texture": str(texture)})
            missing += 1
            continue
        try:
            neutral_mips = _solid_bc4_texturemips_for_descriptor_value(texture, mips, byte_value)
            if mips.read_bytes() == neutral_mips:
                items.append({"stem": stem, "status": "current", "texture": str(texture), "texturemips": str(mips), "value": byte_value})
                skipped += 1
                continue
            _write_game_bytes(mips, neutral_mips, writer, label="ready template neutral material map", backup=False)
            items.append({"stem": stem, "status": "updated", "texture": str(texture), "texturemips": str(mips), "value": byte_value, "bytes": len(neutral_mips)})
            updated += 1
        except Exception as exc:
            items.append({"stem": stem, "status": "skipped", "reason": str(exc), "texture": str(texture), "texturemips": str(mips)})
            skipped += 1

    return {"updated": updated, "skipped": skipped, "missing": missing, "items": items}


def localize_external_ext_skin_material_textures(
    root: Path,
    target_root: Path,
    material_path: Path,
    new_skin_rel: str,
    new_skin_id: str,
    overwrite: bool = False,
    writer: GameFileWriter | None = None,
) -> tuple[list[str], list[str]]:
    """Copy externally referenced EXT_SKIN textures into the new skin with neutral masks."""
    if not material_path.exists():
        return [], []
    new_skin_prefix = norm_slashes(new_skin_rel).lower().rstrip("\\") + "\\"
    copied: list[str] = []
    patched: list[str] = []
    replacements: dict[str, str] = {}
    try:
        material = inspect_material(material_path, root=root)
    except Exception:
        return copied, patched
    for slot in material.get("texture_slots", []):
        slot_name = str(slot.get("name") or "")
        value = norm_slashes(str(slot.get("value") or "").strip())
        if ".texture" not in value.lower():
            continue
        is_skin_ref = "\\skins\\" in value.lower()
        is_base_color_ref = slot_name == "Base_BaseColorMap"
        if not is_skin_ref and not is_base_color_ref:
            continue
        source_text = str(slot.get("resolved_path") or "").strip()
        source_texture = Path(source_text) if source_text else material_resolve_ref(root, value)
        if not source_texture or not source_texture.exists() or source_texture.suffix.lower() != ".texture":
            continue
        stem = source_texture.stem.lower()
        if not stem.startswith("ext_skin"):
            continue
        if value.lower().startswith(new_skin_prefix):
            continue
        source_mips = source_texture.with_suffix(".texturemips")
        if not source_mips.exists():
            continue
        target_name = (
            paint_only_livery.TEXTURE_FILENAMES["base"]
            if is_base_color_ref
            else source_texture.name
        )
        target_rel = norm_slashes(f"{new_skin_rel}\\{target_name}")
        target_texture = content_to_disk(target_root, target_rel)
        target_mips = target_texture.with_suffix(".texturemips")
        if target_texture.exists() and not overwrite:
            pass
        else:
            _copy_game_file(source_texture, target_texture, writer, label="skin-local ext_skin texture descriptor")
            localize_texture_descriptor_source(target_texture, new_skin_id, writer=writer)
            copied.append(str(target_texture))
        if target_mips.exists() and not overwrite:
            pass
        elif stem == "ext_skin_d":
            _copy_game_file(source_mips, target_mips, writer, label="skin-local ext_skin_d texturemips")
            _write_black_spat_texturemips(target_texture, writer=writer)
            copied.append(str(target_mips))
        else:
            solid_name = _neutral_solid_for_ext_skin_texture(stem)
            try:
                solid_mips, _format_name, _note = _neutral_ext_skin_texturemips_for_descriptor(
                    target_texture,
                    source_mips,
                    solid_name,
                    slot_name,
                )
                _write_game_bytes(target_mips, solid_mips, writer, label="skin-local neutral ext_skin texture")
            except Exception:
                _copy_game_file(source_mips, target_mips, writer, label="skin-local ext_skin texturemips fallback")
            copied.append(str(target_mips))
        replacements[_normalize_exact_text(value)] = target_rel
    if replacements:
        old_data = material_path.read_bytes()
        new_data = patch_strings_recursive_exact(old_data, replacements)
        if new_data != old_data:
            _write_game_bytes(material_path, new_data, writer, label="skin-local ext_skin material references", backup=False)
            patched.append(str(material_path))
    return copied, patched


def prune_copied_skin_files_to_references(root: Path, skin_folder: Path, skin_rel: str, seed_blobs: list[bytes], copied_files: list[str], writer: GameFileWriter | None = None) -> list[str]:
    """Remove copied source-skin leftovers that are not reachable from the new design."""
    if not skin_folder.exists():
        return []
    skin_root = skin_folder.resolve()
    skin_prefix = norm_slashes(skin_rel).lower().rstrip("\\") + "\\"
    copied_paths: set[Path] = set()
    for entry in copied_files:
        try:
            path = Path(entry).resolve()
            path.relative_to(skin_root)
        except Exception:
            continue
        if path.is_file():
            copied_paths.add(path)

    banner_workflow_files = {
        path
        for path in copied_paths
        if "banner" in path.name.lower() or "ext_banner" in path.name.lower()
    }

    required: set[Path] = set()
    queue: list[str] = []
    seen_refs: set[str] = set()

    def enqueue_from_blob(raw: bytes) -> None:
        for _path, value in collect_proto_strings(raw):
            for _start, ref in _extract_content_references(value):
                normalized = norm_slashes(ref)
                if normalized.lower().startswith(skin_prefix):
                    queue.append(normalized)

    for blob in seed_blobs:
        enqueue_from_blob(blob)

    while queue:
        ref = norm_slashes(queue.pop(0))
        key = ref.lower()
        if key in seen_refs:
            continue
        seen_refs.add(key)
        if not key.startswith(skin_prefix):
            continue
        disk = resolve_content_path(root, ref).resolve()
        try:
            disk.relative_to(skin_root)
        except Exception:
            continue
        required.add(disk)
        if disk.suffix.lower() == ".texture":
            mips = disk.with_suffix(".texturemips")
            if mips.exists():
                required.add(mips.resolve())
        if disk.exists() and disk.is_file() and disk.suffix.lower() in {".material", ".texture", ".design", ".cardesign"}:
            try:
                enqueue_from_blob(disk.read_bytes())
            except Exception:
                pass

    removed: list[str] = []
    for path in sorted(copied_paths - required - banner_workflow_files, key=lambda item: len(item.parts), reverse=True):
        try:
            if path.exists() and path.is_file():
                if writer:
                    writer.delete_file(path, label="prune copied skin file", backup=False)
                else:
                    path.unlink()
                removed.append(str(path))
        except Exception:
            continue

    for directory in sorted([p for p in skin_folder.rglob("*") if p.is_dir()], key=lambda item: len(item.parts), reverse=True):
        try:
            if not any(directory.iterdir()):
                directory.rmdir()
        except Exception:
            pass
    return removed


def find_actor_file(root: Path, car_content_path: str | None) -> Path | None:
    if not car_content_path:
        return None
    parts = split_content_path(car_content_path)
    car_folder = parts[-1]
    candidate = resolve_content_path(root, f"{car_content_path}\\{car_folder}.actor")
    if candidate.exists(): return candidate
    content_root = root / "content" / "cars"
    if not content_root.exists():
        return None
    try:
        for p in content_root.rglob(f"{car_folder}.actor"):
            if p.is_file(): return p
    except Exception:
        pass
    return None


def _actor_scan_ascii_strings(data: bytes, min_len: int = 4) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    start: int | None = None
    for idx, byte in enumerate(data + b"\x00"):
        if 32 <= byte <= 126:
            if start is None:
                start = idx
            continue
        if start is not None and idx - start >= min_len:
            raw = data[start:idx]
            rows.append(
                {
                    "offset": start,
                    "offset_hex": f"0x{start:X}",
                    "length": len(raw),
                    "text": raw.decode("ascii", errors="replace"),
                    "encoding": "ascii",
                }
            )
        start = None
    return rows


def _actor_category_for_text(text: str) -> str:
    lower = text.lower()
    suffix_map = {
        ".car": "car_data",
        ".cardesign": "cardesign",
        ".design": "design",
        ".visualcarpreset": "visual_preset",
        ".mechanicalcarpreset": "mechanical_preset",
        ".compatiblepart": "compatible_part",
        ".compatiblerim": "compatible_rim",
        ".mesh": "mesh",
        ".material": "material",
        ".texture": "texture",
        ".texturemips": "texture_mips",
        ".tyretextures": "tyre_textures",
        ".table": "color_table",
        ".oemmultilayercolor": "color",
        ".oemsinglelayercolor": "color",
        ".animation": "animation",
        ".particleemitter": "particle",
        ".bank": "sound_bank",
        ".html": "display",
        ".cardescription": "description",
        ".cardisplaysystem": "instrument",
        ".carlightingsystem": "instrument",
        ".carledsystem": "instrument",
        ".carwipersystem": "instrument",
        ".carexhaustbackfire": "instrument",
    }
    for suffix, category in suffix_map.items():
        if lower.endswith(suffix):
            return category
    if lower.startswith("event:/"):
        return "sound_event"
    if "\\" in text or "/" in text:
        return "path"
    return "label"


def _extract_content_references(text: str) -> list[tuple[int, str]]:
    endings = (
        ".car",
        ".cardesign",
        ".design",
        ".visualcarpreset",
        ".mechanicalcarpreset",
        ".compatiblepart",
        ".compatiblerim",
        ".mesh",
        ".material",
        ".texturemips",
        ".texture",
        ".tyretextures",
        ".table",
        ".oemmultilayercolor",
        ".oemsinglelayercolor",
        ".animation",
        ".particleemitter",
        ".bank",
        ".html",
        ".cardescription",
        ".cardisplaysystem",
        ".carlightingsystem",
        ".carledsystem",
        ".carwipersystem",
        ".carexhaustbackfire",
    )
    ordered_endings = sorted(endings, key=len, reverse=True)
    pattern = re.compile(r"content\\[A-Za-z0-9_ .\\/-]+?(?:" + "|".join(re.escape(e) for e in ordered_endings) + r")", re.I)
    return [(m.start(), m.group(0)) for m in pattern.finditer(text)]


def _actor_decode_scalar(wire: int, value: Any) -> dict[str, Any]:
    if wire == 0:
        return {"kind": "varint", "value": int(value)}
    if wire == 1:
        raw = bytes(value)
        out: dict[str, Any] = {"kind": "fixed64", "hex": raw.hex(" ")}
        if len(raw) == 8:
            out["float64_le"] = struct.unpack("<d", raw)[0]
        return out
    if wire == 5:
        raw = bytes(value)
        out = {"kind": "fixed32", "hex": raw.hex(" ")}
        if len(raw) == 4:
            out["float32_le"] = struct.unpack("<f", raw)[0]
        return out
    return {"kind": f"wire_{wire}", "value": value}


def _actor_read_field_records(data: bytes, base_offset: int = 0) -> list[dict[str, Any]]:
    pos = 0
    records: list[dict[str, Any]] = []
    while pos < len(data):
        field_start = pos
        key, pos = read_varint(data, pos)
        number = key >> 3
        wire = key & 7
        value_start = pos
        if number <= 0:
            raise ValueError("invalid protobuf field number")
        if wire == 0:
            value, pos = read_varint(data, pos)
        elif wire == 1:
            if pos + 8 > len(data):
                raise EOFError("fixed64 outside buffer")
            value = data[pos:pos + 8]
            pos += 8
        elif wire == 2:
            length, pos = read_varint(data, pos)
            value_start = pos
            if pos + length > len(data):
                raise EOFError("length-delimited field outside buffer")
            value = data[pos:pos + length]
            pos += length
        elif wire == 5:
            if pos + 4 > len(data):
                raise EOFError("fixed32 outside buffer")
            value = data[pos:pos + 4]
            pos += 4
        else:
            raise ValueError(f"unsupported wire type {wire}")
        records.append(
            {
                "number": number,
                "wire": wire,
                "field_offset": base_offset + field_start,
                "field_offset_hex": f"0x{base_offset + field_start:X}",
                "value_offset": base_offset + value_start,
                "value_offset_hex": f"0x{base_offset + value_start:X}",
                "end_offset": base_offset + pos,
                "length": pos - value_start,
                "value": value,
            }
        )
    return records


def _actor_try_read_field_records(data: bytes, base_offset: int = 0) -> list[dict[str, Any]] | None:
    if not data:
        return None
    try:
        records = _actor_read_field_records(data, base_offset)
    except Exception:
        return None
    return records if records else None


def _actor_walk_records(data: bytes, base_offset: int = 0, path: str = "", depth: int = 0, max_depth: int = 8) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
    records = _actor_try_read_field_records(data, base_offset) or []
    tree: list[dict[str, Any]] = []
    strings: list[dict[str, Any]] = []
    for record in records:
        number = int(record["number"])
        field_path = f"{path}.{number}" if path else str(number)
        node: dict[str, Any] = {
            "path": field_path,
            "number": number,
            "wire": int(record["wire"]),
            "field_offset": record["field_offset"],
            "field_offset_hex": record["field_offset_hex"],
            "value_offset": record["value_offset"],
            "value_offset_hex": record["value_offset_hex"],
            "length": record["length"],
        }
        if record["wire"] == 2:
            raw = bytes(record["value"])
            text = is_printable_utf8(raw)
            if text is not None and text.strip():
                node["kind"] = "string"
                node["text"] = text
                node["category"] = _actor_category_for_text(text)
                strings.append(
                    {
                        "path": field_path,
                        "offset": record["value_offset"],
                        "offset_hex": record["value_offset_hex"],
                        "length": len(raw),
                        "text": text,
                        "category": node["category"],
                        "source": "protobuf",
                    }
                )
            elif depth < max_depth:
                children, child_strings = _actor_walk_records(raw, int(record["value_offset"]), field_path, depth + 1, max_depth)
                if children:
                    node["kind"] = "message"
                    node["children"] = children
                    strings.extend(child_strings)
                else:
                    node["kind"] = "bytes"
                    node["hex_preview"] = raw[:32].hex(" ")
            else:
                node["kind"] = "bytes"
                node["hex_preview"] = raw[:32].hex(" ")
        else:
            node.update(_actor_decode_scalar(int(record["wire"]), record["value"]))
        tree.append(node)
    return tree, strings


def inspect_actor_readable(actor_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    path = Path(actor_path)
    data = path.read_bytes()
    tree, proto_strings = _actor_walk_records(data)
    raw_strings = _actor_scan_ascii_strings(data)
    seen = {(s["offset"], s["length"], s["text"]) for s in proto_strings}
    extra_raw_strings = [
        {**s, "category": _actor_category_for_text(s["text"]), "source": "raw_scan"}
        for s in raw_strings
        if (s["offset"], s["length"], s["text"]) not in seen
    ]
    all_strings = sorted(proto_strings + extra_raw_strings, key=lambda row: (int(row["offset"]), str(row["text"])))
    categories: dict[str, list[dict[str, Any]]] = {}
    for row in all_strings:
        categories.setdefault(str(row["category"]), []).append(row)
    return {
        "file": str(path),
        "size_bytes": len(data),
        "strings": all_strings,
        "categories": categories,
        "field_tree": tree,
        "cardesigns": actor_list_cardesigns(data),
        "visual_presets": actor_list_visualpresets(data),
        "mechanical_presets": actor_list_mechanicalpresets(data),
        "numberplates": _actor_extract_numberplates(data),
        "numberplate_placements": _actor_extract_numberplate_placements(data),
        "numberplate_templates": list_numberplate_templates(root),
        "numberplate_missing_resources": validate_actor_numberplate_resources(path, data),
    }


def actor_readable_summary(result: dict[str, Any]) -> str:
    lines = [
        f"Actor: {Path(str(result.get('file'))).name}",
        f"Size: {int(result.get('size_bytes') or 0):,} bytes",
        f"Readable strings: {len(result.get('strings', []))}",
        f"Cardesigns: {len(result.get('cardesigns', []))}",
        f"Visual presets: {len(result.get('visual_presets', []))}",
        f"Mechanical presets: {len(result.get('mechanical_presets', []))}",
        f"Numberplates: {len(result.get('numberplates', []))}",
        f"Numberplate placements: {len(result.get('numberplate_placements', []))}",
        f"Missing numberplate resources: {len(result.get('numberplate_missing_resources', []))}",
        "",
        "Readable data by category:",
    ]
    categories = result.get("categories", {})
    if isinstance(categories, dict):
        for category in sorted(categories):
            rows = categories.get(category) or []
            lines.append(f"  {category}: {len(rows)}")
    lines.extend(["", "Strings:"])
    for row in result.get("strings", []):
        text = str(row.get("text", "")).replace("\r", "\\r").replace("\n", "\\n")
        prefix = f"{row.get('offset_hex')} [{row.get('category')}]"
        path = row.get("path")
        if path:
            prefix += f" field {path}"
        lines.append(f"{prefix}: {text}")
    if result.get("numberplates"):
        lines.extend(["", "Numberplates:"])
        for row in result.get("numberplates", []):
            lines.append(
                f"  [{row.get('index')}] bg={row.get('background_path') or '-'} | font={row.get('font_path') or '-'} | "
                f"v8={row.get('value_8')!s} v9={row.get('value_9')!s} v10={row.get('value_10')!s} v11={row.get('value_11')!s}"
            )
    if result.get("numberplate_missing_resources"):
        lines.extend(["", "Missing numberplate resources:"])
        for row in result.get("numberplate_missing_resources", []):
            lines.append(f"  - {row}")
    if result.get("numberplate_placements"):
        lines.extend(["", "Numberplate placements:"])
        for row in result.get("numberplate_placements", []):
            pos = ", ".join("-" if value is None else f"{float(value):.3f}" for value in (row.get("position") or []))
            rot = ", ".join("-" if value is None else f"{float(value):.3f}" for value in (row.get("rotation") or []))
            scale = ", ".join("-" if value is None else f"{float(value):.3f}" for value in (row.get("scale") or []))
            lines.append(
                f"  [{row.get('index')}] {row.get('label') or '-'} | pos=({pos}) rot=({rot}) scale=({scale}) "
                f"| f1={row.get('field_1')!s} f2={row.get('field_2')!s}"
            )
    return "\n".join(lines)


def actor_list_cardesigns(actor_data: bytes) -> list[str]:
    out: list[str] = []
    for f in parse_fields(actor_data):
        if f.number != 103 or f.wire != 2: continue
        for pf in parse_fields(f.value):
            if pf.number != 13 or pf.wire != 2: continue
            for rf in parse_fields(pf.value):
                if rf.number == 9 and rf.wire == 2:
                    text = is_printable_utf8(rf.value)
                    if text: out.append(text)
    return out


def add_cardesign_to_actor_data(actor_data: bytes, new_cardesign_rel: str) -> tuple[bytes, bool]:
    new_rel = norm_slashes(new_cardesign_rel)
    actor_fields = parse_fields(actor_data)
    changed = False
    new_actor_fields: list[ProtoField] = []
    for f in actor_fields:
        if f.number != 103 or f.wire != 2:
            new_actor_fields.append(f); continue
        payload_fields = parse_fields(f.value)
        new_payload_fields: list[ProtoField] = []
        patched_resource_block = False
        for pf in payload_fields:
            if pf.number != 13 or pf.wire != 2:
                new_payload_fields.append(pf); continue
            resource_fields = parse_fields(pf.value)
            existing = [is_printable_utf8(rf.value) for rf in resource_fields if rf.number == 9 and rf.wire == 2]
            if new_rel in existing:
                new_payload_fields.append(pf); patched_resource_block = True; continue
            insert_at = None
            for idx, rf in enumerate(resource_fields):
                if rf.number == 9 and rf.wire == 2:
                    insert_at = idx + 1
            new_field = ProtoField(9, 2, new_rel.encode("utf-8"))
            if insert_at is None:
                resource_fields.append(new_field)
            else:
                resource_fields.insert(insert_at, new_field)
            new_payload_fields.append(ProtoField(pf.number, pf.wire, build_fields(resource_fields)))
            changed = True; patched_resource_block = True
        new_actor_fields.append(ProtoField(f.number, f.wire, build_fields(new_payload_fields)) if patched_resource_block else f)
    return (build_fields(new_actor_fields), changed) if changed else (actor_data, False)


def remove_cardesign_from_actor_data(actor_data: bytes, cardesign_rel: str) -> tuple[bytes, bool]:
    target = norm_slashes(cardesign_rel)
    actor_fields = parse_fields(actor_data)
    changed = False
    new_actor_fields: list[ProtoField] = []
    for field in actor_fields:
        if field.number != 103 or field.wire != 2:
            new_actor_fields.append(field)
            continue
        payload_fields = parse_fields(field.value)
        new_payload_fields: list[ProtoField] = []
        patched_resource_block = False
        for payload_field in payload_fields:
            if payload_field.number != 13 or payload_field.wire != 2:
                new_payload_fields.append(payload_field)
                continue
            resource_fields = parse_fields(payload_field.value)
            kept_resource_fields: list[ProtoField] = []
            removed_here = False
            for resource_field in resource_fields:
                if resource_field.number == 9 and resource_field.wire == 2:
                    text = is_printable_utf8(resource_field.value)
                    if text is not None and norm_slashes(text) == target:
                        changed = True
                        removed_here = True
                        continue
                kept_resource_fields.append(resource_field)
            if removed_here:
                new_payload_fields.append(ProtoField(payload_field.number, payload_field.wire, build_fields(kept_resource_fields)))
                patched_resource_block = True
            else:
                new_payload_fields.append(payload_field)
        new_actor_fields.append(ProtoField(field.number, field.wire, build_fields(new_payload_fields)) if patched_resource_block else field)
    return (build_fields(new_actor_fields), True) if changed else (actor_data, False)


def patch_actor_file(actor_path: Path, new_cardesign_rel: str, writer: GameFileWriter | None = None) -> tuple[bool, Path | None]:
    old_data = actor_path.read_bytes()
    new_data, changed = add_cardesign_to_actor_data(old_data, new_cardesign_rel)
    if not changed:
        return False, None
    backup = _write_game_bytes(actor_path, new_data, writer, label="actor cardesign registration")
    return True, backup


def remove_actor_cardesign_file(actor_path: Path, cardesign_rel: str, writer: GameFileWriter | None = None) -> tuple[bool, Path | None]:
    old_data = actor_path.read_bytes()
    new_data, changed = remove_cardesign_from_actor_data(old_data, cardesign_rel)
    if not changed:
        return False, None
    backup = _write_game_bytes(actor_path, new_data, writer, label="actor cardesign removal")
    return True, backup

def actor_list_resources(actor_data: bytes, field_number: int, suffix: str | None = None) -> list[str]:
    out: list[str] = []
    for f in parse_fields(actor_data):
        if f.number != 103 or f.wire != 2:
            continue
        for pf in parse_fields(f.value):
            if pf.number != 13 or pf.wire != 2:
                continue
            for rf in parse_fields(pf.value):
                if rf.number == field_number and rf.wire == 2:
                    text = is_printable_utf8(rf.value)
                    if text and (suffix is None or text.lower().endswith(suffix.lower())):
                        out.append(text)
    return out


def actor_list_visualpresets(actor_data: bytes) -> list[str]:
    return actor_list_resources(actor_data, 11, ".visualcarpreset")


def actor_list_mechanicalpresets(actor_data: bytes) -> list[str]:
    return actor_list_resources(actor_data, 10, ".mechanicalcarpreset")


def actor_list_compatibleparts(actor_data: bytes) -> list[str]:
    return actor_list_resources(actor_data, 21, ".compatiblepart")


def _actor_first_payload_records(actor_data: bytes) -> tuple[dict[str, Any] | None, list[dict[str, Any]]]:
    actor_records = _actor_read_field_records(actor_data, 0)
    top_record = next((record for record in actor_records if record["number"] == 103 and record["wire"] == 2), None)
    if top_record is None:
        return None, []
    payload_records = _actor_read_field_records(bytes(top_record["value"]), int(top_record["value_offset"]))
    return top_record, payload_records


def _actor_signed64_from_varint(value: int | None) -> int | None:
    if value is None:
        return None
    ivalue = int(value)
    return ivalue - (1 << 64) if ivalue >= (1 << 63) else ivalue


def _actor_encode_signed64_varint(value: int) -> int:
    ivalue = int(value)
    return ivalue if ivalue >= 0 else ((1 << 64) + ivalue)


def _actor_extract_numberplates(actor_data: bytes) -> list[dict[str, Any]]:
    _, payload_records = _actor_first_payload_records(actor_data)
    entries: list[dict[str, Any]] = []
    for index, record in enumerate([row for row in payload_records if row["number"] == 51 and row["wire"] == 2]):
        fields = parse_fields(bytes(record["value"]))
        by_number: dict[int, ProtoField] = {field.number: field for field in fields}
        overlay = [None, None, None, None]
        field7 = by_number.get(7)
        if field7 and field7.wire == 2:
            # Actor numberplate field 7 stores the number/font tint as four linear RGBA floats.
            # The font texture supplies glyph shape/alpha; these values define the visible text color.
            for nested in parse_fields(field7.value):
                if nested.wire == 5 and 1 <= nested.number <= 4 and isinstance(nested.value, (bytes, bytearray)) and len(nested.value) == 4:
                    overlay[nested.number - 1] = float(struct.unpack("<f", bytes(nested.value))[0])
        entries.append(
            {
                "index": index,
                "field_offset": record["field_offset"],
                "field_offset_hex": record["field_offset_hex"],
                "value_offset": record["value_offset"],
                "value_offset_hex": record["value_offset_hex"],
                "font_path": is_printable_utf8(by_number.get(4).value) if by_number.get(4) and by_number.get(4).wire == 2 else None,
                "background_path": is_printable_utf8(by_number.get(5).value) if by_number.get(5) and by_number.get(5).wire == 2 else None,
                "opacity": struct.unpack("<f", bytes(by_number[3].value))[0] if by_number.get(3) and by_number[3].wire == 5 else None,
                "overlay": overlay,
                "value_8": int(by_number[8].value) if by_number.get(8) and by_number[8].wire == 0 else None,
                "value_9": int(by_number[9].value) if by_number.get(9) and by_number[9].wire == 0 else None,
                "value_10": int(by_number[10].value) if by_number.get(10) and by_number[10].wire == 0 else None,
                "value_11": int(by_number[11].value) if by_number.get(11) and by_number[11].wire == 0 else None,
                "value_15": struct.unpack("<f", bytes(by_number[15].value))[0] if by_number.get(15) and by_number[15].wire == 5 else None,
                "value_16": _actor_signed64_from_varint(by_number[16].value) if by_number.get(16) and by_number[16].wire == 0 else None,
            }
        )
    return entries


def _actor_extract_numberplate_placements(actor_data: bytes) -> list[dict[str, Any]]:
    _, payload_records = _actor_first_payload_records(actor_data)
    container = next((row for row in payload_records if row["number"] == 52 and row["wire"] == 2), None)
    if container is None:
        return []
    placements: list[dict[str, Any]] = []
    for index, record in enumerate(_actor_read_field_records(bytes(container["value"]), int(container["value_offset"]))):
        if record["wire"] != 2:
            continue
        fields = parse_fields(bytes(record["value"]))
        by_number: dict[int, ProtoField] = {field.number: field for field in fields}

        def _vec(field_number: int, size: int) -> list[float | None]:
            source = by_number.get(field_number)
            values: list[float | None] = [None] * size
            if not source or source.wire != 2:
                return values
            for nested in parse_fields(source.value):
                if nested.wire == 5 and 1 <= nested.number <= size and isinstance(nested.value, (bytes, bytearray)) and len(nested.value) == 4:
                    values[nested.number - 1] = float(struct.unpack("<f", bytes(nested.value))[0])
            return values

        placements.append(
            {
                "index": index,
                "outer_field_number": int(record["number"]),
                "field_offset": record["field_offset"],
                "field_offset_hex": record["field_offset_hex"],
                "value_offset": record["value_offset"],
                "value_offset_hex": record["value_offset_hex"],
                "field_1": int(by_number[1].value) if by_number.get(1) and by_number[1].wire == 0 else None,
                "field_2": int(by_number[2].value) if by_number.get(2) and by_number[2].wire == 0 else None,
                "label": is_printable_utf8(by_number.get(3).value) if by_number.get(3) and by_number[3].wire == 2 else None,
                "position": _vec(4, 3),
                "rotation": _vec(5, 3),
                "scale": _vec(6, 3),
            }
        )
    return placements


def extract_visual_numberplate_selector(data: bytes) -> dict[int, int]:
    for field in parse_fields(data):
        if field.number != 15 or field.wire != 2:
            continue
        selector: dict[int, int] = {}
        try:
            for nested in parse_fields(field.value):
                if nested.wire == 0:
                    selector[int(nested.number)] = int(nested.value)
        except Exception:
            return {}
        return selector
    return {}


def edit_visual_numberplate_selector_data(data: bytes, selector: dict[int, int]) -> bytes:
    selector = {int(surface): int(index) for surface, index in selector.items() if int(index) > 0}
    fields = parse_fields(data)
    changed = False
    new_fields: list[ProtoField] = []
    selector_written = False
    for field in fields:
        if field.number == 15 and field.wire == 2:
            nested = parse_fields(field.value) if field.value else []
            for surface, index in selector.items():
                before = [(row.number, row.wire, row.value) for row in nested]
                nested = _actor_set_or_append_field(nested, surface, 0, index)
                if before != [(row.number, row.wire, row.value) for row in nested]:
                    changed = True
            new_value = build_fields(nested)
            if new_value != bytes(field.value):
                changed = True
            new_fields.append(ProtoField(15, 2, new_value))
            selector_written = True
        else:
            new_fields.append(field)
    if not selector_written and selector:
        nested = [ProtoField(surface, 0, index) for surface, index in sorted(selector.items())]
        new_fields.append(ProtoField(15, 2, build_fields(nested)))
        changed = True
    return build_fields(new_fields) if changed else data


def replace_visual_numberplate_selector_data(data: bytes, selector: dict[int, int]) -> bytes:
    new_data, _changed = advanced_numberplate_state.set_visual_selector(data, selector)
    return new_data


def update_visual_numberplate_selector_file(visual_path: str | Path, selector: dict[int, int], writer: GameFileWriter | None = None) -> tuple[bool, Path | None]:
    path = Path(visual_path)
    old_data = path.read_bytes()
    new_data = edit_visual_numberplate_selector_data(old_data, selector)
    if new_data == old_data:
        return False, None
    backup = _write_game_bytes(path, new_data, writer, label="visual numberplate selector")
    return True, backup


def replace_visual_numberplate_selector_file(visual_path: str | Path, selector: dict[int, int], writer: GameFileWriter | None = None) -> tuple[bool, Path | None]:
    path = Path(visual_path)
    old_data = path.read_bytes()
    new_data = replace_visual_numberplate_selector_data(old_data, selector)
    if new_data == old_data:
        return False, None
    backup = _write_game_bytes(path, new_data, writer, label="visual numberplate selector reset")
    return True, backup


def _coerce_actor_numberplate_text_color(values: list[Any] | tuple[Any, ...] | None) -> list[float] | None:
    if not isinstance(values, (list, tuple)) or len(values) < 4:
        return None
    color: list[float] = []
    for index, value in enumerate(values[:4]):
        try:
            numeric = float(value)
        except Exception:
            return None
        color.append(max(0.0, min(1.0, numeric)))
    return color


def _actor_numberplate_text_color(plate: dict[str, Any] | None) -> list[float] | None:
    if not isinstance(plate, dict):
        return None
    return _coerce_actor_numberplate_text_color(plate.get("overlay"))


def _actor_clone_numberplate_entry(
    raw: bytes,
    font_path: str | None = None,
    background_path: str | None = None,
    text_color: list[float] | None = None,
    text_fields: dict[str, Any] | None = None,
) -> bytes:
    payload: dict[str, Any] = {}
    if font_path is not None:
        payload["font_path"] = font_path
    if background_path is not None:
        payload["background_path"] = background_path
    if text_color is not None:
        payload["overlay"] = text_color
    if text_fields:
        payload["text_fields"] = text_fields
    return _actor_update_numberplate_entry_bytes(raw, payload) if payload else raw


def _actor_clone_placement_entry(raw: bytes, numberplate_index_1based: int) -> bytes:
    fields = parse_fields(raw)
    fields = _actor_set_or_append_field(fields, 1, 0, 1)
    fields = _actor_set_or_append_field(fields, 2, 0, int(numberplate_index_1based))
    return build_fields(fields)


def _actor_placement_record_from_raw(raw: bytes, surface: int | None = None, index: int | None = None) -> dict[str, Any]:
    fields = parse_fields(raw)
    by_number: dict[int, ProtoField] = {field.number: field for field in fields}

    def _vec(field_number: int, size: int) -> list[float | None]:
        source = by_number.get(field_number)
        values: list[float | None] = [None] * size
        if not source or source.wire != 2:
            return values
        for nested in parse_fields(source.value):
            if nested.wire == 5 and 1 <= nested.number <= size and isinstance(nested.value, (bytes, bytearray)) and len(nested.value) == 4:
                values[nested.number - 1] = float(struct.unpack("<f", bytes(nested.value))[0])
        return values

    return {
        "index": index,
        "outer_field_number": surface,
        "surface": surface,
        "field_1": int(by_number[1].value) if by_number.get(1) and by_number[1].wire == 0 else None,
        "field_2": int(by_number[2].value) if by_number.get(2) and by_number[2].wire == 0 else None,
        "label": is_printable_utf8(by_number.get(3).value) if by_number.get(3) and by_number[3].wire == 2 else None,
        "position": _vec(4, 3),
        "rotation": _vec(5, 3),
        "scale": _vec(6, 3),
    }


def _actor_clone_placement_entry_for_surface(
    raw: bytes,
    numberplate_index_1based: int,
    surface: int,
    calibration: dict[str, Any] | None = None,
) -> bytes:
    cloned = _actor_clone_placement_entry(raw, numberplate_index_1based)
    if not isinstance(calibration, dict) or not isinstance(calibration.get("actorBaseline"), dict):
        return cloned
    fallback = _actor_placement_record_from_raw(cloned, surface=surface)
    position, rotation, scale, _source = _numberplate_actor_baseline(calibration, fallback)
    return _actor_update_placement_entry_bytes(
        cloned,
        {
            "position": position,
            "rotation": rotation,
            "scale": scale,
        },
    )


def _actor_build_placement_entry(
    numberplate_index_1based: int,
    label: str,
    position: list[float | None] | tuple[float | None, ...] | None = None,
    rotation: list[float | None] | tuple[float | None, ...] | None = None,
    scale: list[float | None] | tuple[float | None, ...] | None = None,
) -> bytes:
    fields: list[ProtoField] = [
        ProtoField(1, 0, 1),
        ProtoField(2, 0, int(numberplate_index_1based)),
        ProtoField(3, 2, str(label or "").strip().encode("utf-8")),
    ]

    def add_vector(field_number: int, values: list[float | None] | tuple[float | None, ...] | None, size: int) -> None:
        if not isinstance(values, (list, tuple)):
            return
        normalized = list(values[:size]) + [None] * max(0, size - len(values))
        if not any(value not in (None, "") for value in normalized[:size]):
            return
        fields.append(ProtoField(field_number, 2, _actor_update_float_vector_message(b"", normalized[:size], size)))

    add_vector(4, position, 3)
    add_vector(5, rotation, 3)
    add_vector(6, scale, 3)
    return build_fields(fields)


def _actor_synthetic_hood_placement_entry(numberplate_index_1based: int, calibration: dict[str, Any]) -> bytes:
    if not isinstance(calibration, dict) or not isinstance(calibration.get("actorBaseline"), dict):
        raise RuntimeError("Synthetic hood numberplate placement needs a HOOD actorBaseline")
    fallback = {
        "label": "HOOD",
        "position": [None, None, None],
        "rotation": [None, None, None],
        "scale": [None, None, None],
    }
    position, rotation, scale, _source = _numberplate_actor_baseline(calibration, fallback)
    required = {
        "hood actor baseline position.z": position[2] if len(position) > 2 else None,
        "hood actor baseline scale.x": scale[0] if scale else None,
        "hood actor baseline scale.z": scale[2] if len(scale) > 2 else None,
    }
    missing = [label for label, value in required.items() if value in (None, "")]
    if missing:
        raise RuntimeError("Synthetic hood numberplate placement is missing " + ", ".join(missing))
    return _actor_build_placement_entry(
        numberplate_index_1based,
        "HOOD",
        position=position,
        rotation=rotation,
        scale=scale,
    )


def _actor_selected_numberplate_index(actor_data: bytes, selector: dict[int, int]) -> int | None:
    placements = _actor_extract_numberplate_placements(actor_data)
    by_surface: dict[int, list[dict[str, Any]]] = {}
    for placement in placements:
        surface = int(placement.get("outer_field_number") or 0)
        if not surface:
            continue
        by_surface.setdefault(surface, []).append(placement)
    plate_indices: set[int] = set()
    for surface, ordinal in selector.items():
        rows = by_surface.get(int(surface), [])
        index = int(ordinal) - 1
        if index < 0 or index >= len(rows):
            continue
        plate_index = rows[index].get("field_2")
        if plate_index:
            plate_indices.add(int(plate_index) - 1)
    if len(plate_indices) == 1:
        return next(iter(plate_indices))
    return None


def _actor_selected_numberplate_indices_by_surface(actor_data: bytes, selector: dict[int, int]) -> dict[int, int]:
    placements = _actor_extract_numberplate_placements(actor_data)
    by_surface: dict[int, list[dict[str, Any]]] = {}
    for placement in placements:
        surface = int(placement.get("outer_field_number") or 0)
        if surface:
            by_surface.setdefault(surface, []).append(placement)
    selected: dict[int, int] = {}
    for surface, ordinal in selector.items():
        rows = by_surface.get(int(surface), [])
        index = int(ordinal) - 1
        if index < 0 or index >= len(rows):
            continue
        plate_index = rows[index].get("field_2")
        if plate_index:
            selected[int(surface)] = int(plate_index) - 1
    return selected


def _actor_selected_placement_records_by_surface(actor_data: bytes, selector: dict[int, int]) -> dict[int, dict[str, Any]]:
    placements = _actor_extract_numberplate_placements(actor_data)
    by_surface: dict[int, list[dict[str, Any]]] = {}
    for placement in placements:
        surface = int(placement.get("outer_field_number") or placement.get("surface") or 0)
        if surface:
            by_surface.setdefault(surface, []).append(placement)
    selected: dict[int, dict[str, Any]] = {}
    for surface, rows in by_surface.items():
        ordinal = int((selector or {}).get(surface) or 1)
        index = max(0, min(ordinal - 1, len(rows) - 1))
        selected[int(surface)] = dict(rows[index])
    return selected


def _actor_selected_placement_raw_by_surface(actor_data: bytes, selector: dict[int, int]) -> dict[int, bytes]:
    _, payload_records = _actor_first_payload_records(actor_data)
    container = next((row for row in payload_records if row["number"] == 52 and row["wire"] == 2), None)
    if container is None:
        return {}
    placement_fields = [field for field in parse_fields(bytes(container["value"])) if field.wire == 2]
    by_surface: dict[int, list[ProtoField]] = {}
    for field in placement_fields:
        by_surface.setdefault(int(field.number), []).append(field)
    selected: dict[int, bytes] = {}
    for surface, rows in by_surface.items():
        ordinal = int(selector.get(surface) or 1)
        index = max(0, min(ordinal - 1, len(rows) - 1))
        selected[surface] = bytes(rows[index].value)
    return selected


def _actor_numberplate_summary_by_index(actor_data: bytes, index: int | None) -> dict[str, Any]:
    if index is None:
        return {}
    numberplates = _actor_extract_numberplates(actor_data)
    if index < 0 or index >= len(numberplates):
        return {}
    return numberplates[index]


def inspect_visual_numberplate_variant(actor_path: str | Path, visual_path: str | Path) -> dict[str, Any]:
    actor = Path(actor_path)
    visual = Path(visual_path)
    actor_data = actor.read_bytes()
    visual_data = visual.read_bytes()
    selector = extract_visual_numberplate_selector(visual_data)
    plate_index = _actor_selected_numberplate_index(actor_data, selector)
    selected_by_surface = _actor_selected_numberplate_indices_by_surface(actor_data, selector)
    selected_raw_by_surface = _actor_selected_placement_raw_by_surface(actor_data, selector)
    for surface, raw in selected_raw_by_surface.items():
        surface_index = int(surface)
        if surface_index not in {1, 3, 4} or surface_index in selected_by_surface:
            continue
        selected_by_surface[surface_index] = _actor_placement_numberplate_index_1based(raw) - 1
    numberplates = _actor_extract_numberplates(actor_data)
    numberplate = numberplates[plate_index] if plate_index is not None and 0 <= plate_index < len(numberplates) else None
    hood_index = selected_by_surface.get(1)
    left_index = selected_by_surface.get(3)
    right_index = selected_by_surface.get(4)
    hood_plate = _actor_numberplate_summary_by_index(actor_data, hood_index)
    left_plate = _actor_numberplate_summary_by_index(actor_data, left_index)
    right_plate = _actor_numberplate_summary_by_index(actor_data, right_index)
    surface_indices = [value for value in (hood_index, left_index, right_index) if value is not None and value >= 0]
    surface_mode = len(surface_indices) >= 2 and len(set(surface_indices)) > 1 and hood_index is not None
    split_mode = not surface_mode and left_index is not None and right_index is not None and left_index != right_index
    font_path = numberplate.get("font_path") if numberplate else ""
    background_path = numberplate.get("background_path") if numberplate else ""
    text_color = _actor_numberplate_text_color(numberplate)
    hood_text_color = _actor_numberplate_text_color(hood_plate)
    left_text_color = _actor_numberplate_text_color(left_plate)
    right_text_color = _actor_numberplate_text_color(right_plate)
    if surface_mode:
        surface_plates = [plate for plate in (hood_plate, left_plate, right_plate) if plate]
        surface_fonts = {str(plate.get("font_path") or "") for plate in surface_plates}
        surface_colors = [_actor_numberplate_text_color(plate) for plate in surface_plates]
        font_path = next(iter(surface_fonts)) if len(surface_fonts) == 1 else ""
        background_path = ""
        text_color = surface_colors[0] if surface_colors and all(color == surface_colors[0] for color in surface_colors) else None
    elif split_mode:
        left_font = str(left_plate.get("font_path") or "")
        right_font = str(right_plate.get("font_path") or "")
        font_path = left_font if left_font == right_font else ""
        background_path = ""
        text_color = left_text_color if left_text_color == right_text_color else None
    return {
        "selector": selector,
        "mode": "surface" if surface_mode else ("split" if split_mode else "same"),
        "selected_by_surface": selected_by_surface,
        "available_numberplate_surfaces": sorted(int(surface) for surface in selected_raw_by_surface if int(surface) in {1, 3, 4}),
        "has_hood_numberplate": 1 in selected_raw_by_surface,
        "numberplate_index": plate_index,
        "numberplate_index_1based": plate_index + 1 if plate_index is not None else None,
        "numberplate_indices": surface_indices if surface_mode else ([value for value in (left_index, right_index) if value is not None and value >= 0] if split_mode else ([plate_index] if plate_index is not None else [])),
        "numberplate_indices_1based": [value + 1 for value in surface_indices] if surface_mode else ([value + 1 for value in (left_index, right_index) if value is not None and value >= 0] if split_mode else ([plate_index + 1] if plate_index is not None else [])),
        "hood_numberplate_index": hood_index,
        "hood_numberplate_index_1based": hood_index + 1 if hood_index is not None else None,
        "left_numberplate_index": left_index,
        "left_numberplate_index_1based": left_index + 1 if left_index is not None else None,
        "right_numberplate_index": right_index,
        "right_numberplate_index_1based": right_index + 1 if right_index is not None else None,
        "font_path": font_path,
        "background_path": background_path,
        "hood_background_path": hood_plate.get("background_path") if hood_plate else "",
        "left_background_path": left_plate.get("background_path") if left_plate else "",
        "right_background_path": right_plate.get("background_path") if right_plate else "",
        "text_color": text_color,
        "textColor": text_color,
        "hood_text_color": hood_text_color,
        "left_text_color": left_text_color,
        "right_text_color": right_text_color,
        "is_custom_variant": bool(any(int(value) > 0 for value in selected_by_surface.values()) and any(int(value) > 1 for value in selector.values())),
    }


def _game_root_from_actor_path(actor_path: Path) -> Path | None:
    parts = list(actor_path.resolve().parts)
    lowered = [part.lower() for part in parts]
    try:
        content_index = lowered.index("content")
    except ValueError:
        return None
    if content_index <= 0:
        return None
    return Path(*parts[:content_index])


def _car_id_from_actor_path(actor_path: Path) -> str:
    parts = list(actor_path.resolve().parts)
    lowered = [part.lower() for part in parts]
    try:
        cars_index = lowered.index("cars")
    except ValueError:
        return ""
    if cars_index + 1 >= len(parts):
        return ""
    return str(parts[cars_index + 1]).strip()


def _numberplate_builtin_calibrations_for_car(car_id: str) -> dict[str, Any]:
    key = str(car_id or "").strip()
    if not key:
        return {}
    path = Path(__file__).resolve().parent / "web" / "assets" / "numberplate_calibrations.json"
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {}
    cars = data.get("cars") if isinstance(data, dict) and isinstance(data.get("cars"), dict) else {}
    direct = cars.get(key)
    if isinstance(direct, dict):
        return direct
    lower_key = key.lower()
    for candidate_key, value in cars.items():
        if str(candidate_key).lower() == lower_key and isinstance(value, dict):
            return value
    return {}


def _numberplate_calibration_for_surface(car_calibrations: dict[str, Any], surface: int) -> dict[str, Any]:
    keys_by_surface = {
        1: ("HOOD",),
        3: ("DOOR_L", "LEFT", "DOOR"),
        4: ("DOOR_R", "RIGHT", "DOOR"),
    }
    keys = keys_by_surface.get(int(surface), ())
    for key in keys:
        value = car_calibrations.get(key)
        if isinstance(value, dict):
            return value
    for raw_key, value in car_calibrations.items():
        base_key = str(raw_key).split(":", 1)[0].upper()
        if base_key in keys and isinstance(value, dict):
            return value
    actor_baselines = car_calibrations.get("__actorBaselines") if isinstance(car_calibrations.get("__actorBaselines"), dict) else {}
    for key in keys:
        value = actor_baselines.get(key)
        if isinstance(value, dict):
            if isinstance(value.get("actorBaseline"), dict):
                return value
            return {"actorBaseline": value}
    return {}


def _numberplate_placement_calibrations_for_actor(actor_path: Path) -> dict[int, dict[str, Any]]:
    car_calibrations = _numberplate_builtin_calibrations_for_car(_car_id_from_actor_path(actor_path))
    if not car_calibrations:
        return {}
    result: dict[int, dict[str, Any]] = {}
    for surface in (1, 3, 4):
        calibration = _numberplate_calibration_for_surface(car_calibrations, surface)
        if isinstance(calibration.get("actorBaseline"), dict):
            result[surface] = calibration
    return result


def _numberplate_calibrated_placement_updates(
    selected_records: dict[int, dict[str, Any]],
    surfaces: list[int] | tuple[int, ...],
    calibrations_by_surface: dict[int, dict[str, Any]],
) -> list[dict[str, Any]]:
    updates: list[dict[str, Any]] = []
    for surface in surfaces:
        calibration = calibrations_by_surface.get(int(surface))
        record = selected_records.get(int(surface), {})
        if not isinstance(calibration, dict) or not isinstance(calibration.get("actorBaseline"), dict):
            continue
        if not isinstance(record, dict) or record.get("index") is None:
            continue
        position, rotation, scale, _source = _numberplate_actor_baseline(calibration, record)
        updates.append(
            {
                "index": int(record.get("index")),
                "position": position,
                "rotation": rotation,
                "scale": scale,
            }
        )
    return updates


def _normalize_actor_resource_input(actor_path: Path, value: str | None) -> str | None:
    text = str(value or "").strip()
    if not text:
        return None
    text = norm_slashes(text)
    lower = text.lower()
    if lower.startswith("content\\"):
        return normalize_game_rel_path(text)
    marker = "\\content\\"
    if marker in lower:
        idx = lower.index(marker) + 1
        return normalize_game_rel_path(text[idx:])
    root = _game_root_from_actor_path(actor_path)
    if root:
        try:
            path = Path(text)
            if path.exists():
                rel = game_rel_from_disk(root, path)
                if rel:
                    return rel
        except Exception:
            pass
    return text


def _actor_resource_exists(actor_path: Path, rel_path: str | None) -> bool:
    text = str(rel_path or "").strip()
    if not text:
        return True
    root = _game_root_from_actor_path(actor_path)
    if root is None:
        return True
    rel = normalize_game_rel_path(text)
    lower = rel.lower()
    if not lower.startswith("content\\"):
        return True
    if (root / rel).exists():
        return True
    try:
        actor_resolved = actor_path.resolve()
        for skin in load_my_skins():
            local_root_text = str(skin.get("local_root") or "").strip()
            game_root_text = str(skin.get("game_root") or "").strip()
            if not local_root_text or not game_root_text:
                continue
            local_root = Path(local_root_text)
            if _path_is_within(actor_resolved, local_root.resolve()):
                game_root = normalize_game_root(game_root_text)
                if (local_root / Path(*split_content_path(rel))).exists():
                    return True
                return (game_root / rel).exists()
    except Exception:
        pass
    return False


def validate_actor_numberplate_resources(actor_path: str | Path, actor_data: bytes | None = None) -> list[str]:
    actor = Path(actor_path)
    data = actor_data if actor_data is not None else actor.read_bytes()
    missing: list[str] = []
    for plate in _actor_extract_numberplates(data):
        index = int(plate.get("index") or 0) + 1
        for key, label in (("font_path", "font"), ("background_path", "background")):
            rel = str(plate.get(key) or "").strip()
            if rel and not _actor_resource_exists(actor, rel):
                missing.append(f"numberplate {index} {label}: {rel}")
    return missing


def _new_actor_numberplate_resource_errors(actor_path: str | Path, before_missing: list[str], actor_data: bytes) -> list[str]:
    before = set(before_missing or [])
    return [row for row in validate_actor_numberplate_resources(actor_path, actor_data) if row not in before]


DEFAULT_NUMBERPLATE_FONT_REL = "content\\cars\\common_assets\\numberplates\\fonts\\alfa_2\\numbers_guilia_2.texture"
DEFAULT_NUMBERPLATE_BACKGROUND_REL = "content\\cars\\common_assets\\numberplates\\backgrounds\\nls_1\\numberplate_nls.texture"


def _repair_actor_missing_numberplate_resources(
    actor_path: str | Path,
    actor_data: bytes,
    protected_indices: list[int] | tuple[int, ...] | set[int] | None = None,
) -> tuple[bytes, dict[str, Any]]:
    actor = Path(actor_path)
    protected = {int(index) for index in (protected_indices or [])}
    updates: list[dict[str, Any]] = []
    repaired: list[dict[str, Any]] = []

    for plate in _actor_extract_numberplates(actor_data):
        index = int(plate.get("index") or 0)
        if index in protected:
            continue
        update: dict[str, Any] = {"index": index}
        for key, label, fallback in (
            ("font_path", "font", DEFAULT_NUMBERPLATE_FONT_REL),
            ("background_path", "background", DEFAULT_NUMBERPLATE_BACKGROUND_REL),
        ):
            rel = str(plate.get(key) or "").strip()
            if not rel or _actor_resource_exists(actor, rel):
                continue
            if not _actor_resource_exists(actor, fallback):
                continue
            update[key] = fallback
            repaired.append(
                {
                    "numberplateIndex": index,
                    "resource": label,
                    "from": rel,
                    "to": fallback,
                }
            )
        if len(update) > 1:
            updates.append(update)

    if not updates:
        return actor_data, {"changed": False, "repairedResources": []}
    new_actor_data, changed = update_actor_numberplate_data(actor_data, numberplates=updates)
    return new_actor_data, {"changed": changed, "repairedResources": repaired if changed else []}


def append_actor_numberplate_variant_data(
    actor_data: bytes,
    font_path: str | None = None,
    background_path: str | None = None,
    text_color: list[float] | None = None,
    source_numberplate_index: int = 0,
    surfaces: tuple[int, ...] = (1, 3, 4),
    placement_calibrations_by_surface: dict[int, dict[str, Any]] | None = None,
) -> tuple[bytes, dict[str, Any]]:
    actor_fields, payload_index, payload_fields = _actor_payload_fields_for_update(actor_data)
    numberplate_fields = [field for field in payload_fields if field.number == 51 and field.wire == 2]
    if not numberplate_fields:
        raise RuntimeError("Actor has no numberplate definition to clone")
    source_index = max(0, min(int(source_numberplate_index), len(numberplate_fields) - 1))
    new_plate_index_1based = len(numberplate_fields) + 1
    cloned_numberplate = _actor_clone_numberplate_entry(bytes(numberplate_fields[source_index].value), font_path=font_path, background_path=background_path, text_color=text_color)

    selector: dict[int, int] = {}
    new_payload_fields: list[ProtoField] = []
    inserted_numberplate = False
    placement_changed = False
    for field in payload_fields:
        new_payload_fields.append(field)
        if not inserted_numberplate and field.number == 51 and field.wire == 2 and field is numberplate_fields[-1]:
            new_payload_fields.append(ProtoField(51, 2, cloned_numberplate))
            inserted_numberplate = True

    patched_payload_fields: list[ProtoField] = []
    for field in new_payload_fields:
        if field.number != 52 or field.wire != 2:
            patched_payload_fields.append(field)
            continue
        placement_fields = parse_fields(field.value)
        counts: dict[int, int] = {}
        clones: list[ProtoField] = []
        seen_surfaces: set[int] = set()
        for placement_field in placement_fields:
            if placement_field.wire != 2:
                continue
            surface = int(placement_field.number)
            counts[surface] = counts.get(surface, 0) + 1
            if surface in surfaces and surface not in seen_surfaces:
                clones.append(
                    ProtoField(
                        surface,
                        2,
                        _actor_clone_placement_entry_for_surface(
                            bytes(placement_field.value),
                            new_plate_index_1based,
                            surface,
                            (placement_calibrations_by_surface or {}).get(surface),
                        ),
                    )
                )
                selector[surface] = counts[surface] + 1
                seen_surfaces.add(surface)
        if not clones:
            patched_payload_fields.append(field)
            continue
        placement_changed = True
        patched_payload_fields.append(ProtoField(52, 2, build_fields(placement_fields + clones)))

    if not inserted_numberplate or not placement_changed or not selector:
        raise RuntimeError("Could not append actor numberplate variant")
    new_actor_fields = list(actor_fields)
    new_actor_fields[payload_index] = ProtoField(103, 2, build_fields(patched_payload_fields))
    return build_fields(new_actor_fields), {
        "numberplate_index": new_plate_index_1based - 1,
        "numberplate_index_1based": new_plate_index_1based,
        "selector": selector,
        "font_path": font_path or "",
        "background_path": background_path or "",
        "text_color": text_color,
        "textColor": text_color,
        "created_by": "LiveryLab",
        "owns_actor_numberplate": True,
    }


def append_actor_numberplate_variant_from_selected_data(
    actor_data: bytes,
    selector: dict[int, int] | None = None,
    font_path: str | None = None,
    background_path: str | None = None,
    text_color: list[float] | None = None,
    surfaces: tuple[int, ...] = (1, 3, 4),
    placement_calibrations_by_surface: dict[int, dict[str, Any]] | None = None,
) -> tuple[bytes, dict[str, Any]]:
    actor_fields, payload_index, payload_fields = _actor_payload_fields_for_update(actor_data)
    numberplate_fields = [field for field in payload_fields if field.number == 51 and field.wire == 2]
    if not numberplate_fields:
        raise RuntimeError("Actor has no numberplate definition to clone")

    selector = selector or {}
    selected_raw = _actor_selected_placement_raw_by_surface(actor_data, selector)
    selected_records = _actor_selected_placement_records_by_surface(actor_data, selector)
    selected_index = _actor_selected_numberplate_index(actor_data, selector)
    if selected_index is None:
        selected_indices = {
            _actor_placement_numberplate_index_1based(raw) - 1
            for surface, raw in selected_raw.items()
            if int(surface) in surfaces and raw
        }
        if len(selected_indices) == 1:
            selected_index = next(iter(selected_indices))
    source_index = max(0, min(int(selected_index if selected_index is not None else 0), len(numberplate_fields) - 1))
    new_plate_index_1based = len(numberplate_fields) + 1
    cloned_numberplate = _actor_clone_numberplate_entry(
        bytes(numberplate_fields[source_index].value),
        font_path=font_path,
        background_path=background_path,
        text_color=text_color,
    )

    new_payload_fields: list[ProtoField] = []
    inserted_numberplate = False
    for field in payload_fields:
        new_payload_fields.append(field)
        if not inserted_numberplate and field.number == 51 and field.wire == 2 and field is numberplate_fields[-1]:
            new_payload_fields.append(ProtoField(51, 2, cloned_numberplate))
            inserted_numberplate = True

    selector_patch: dict[int, int] = {}
    placement_sources: dict[str, dict[str, Any]] = {}
    patched_payload_fields: list[ProtoField] = []
    placement_changed = False
    for field in new_payload_fields:
        if field.number != 52 or field.wire != 2:
            patched_payload_fields.append(field)
            continue
        placement_fields = parse_fields(field.value)
        counts: dict[int, int] = {}
        for placement_field in placement_fields:
            if placement_field.wire == 2:
                counts[int(placement_field.number)] = counts.get(int(placement_field.number), 0) + 1
        clones: list[ProtoField] = []
        target_index = sum(1 for placement_field in placement_fields if placement_field.wire == 2)
        for surface in surfaces:
            raw = selected_raw.get(int(surface))
            if not raw:
                continue
            clones.append(
                ProtoField(
                    int(surface),
                    2,
                    _actor_clone_placement_entry_for_surface(
                        raw,
                        new_plate_index_1based,
                        int(surface),
                        (placement_calibrations_by_surface or {}).get(int(surface)),
                    ),
                )
            )
            selector_patch[int(surface)] = counts.get(int(surface), 0) + 1
            source_record = selected_records.get(int(surface), {})
            placement_sources[str(target_index)] = _numberplate_placement_source_info(source_record, int(surface))
            target_index += 1
        if not clones:
            patched_payload_fields.append(field)
            continue
        placement_changed = True
        patched_payload_fields.append(ProtoField(52, 2, build_fields(placement_fields + clones)))

    if not inserted_numberplate or not placement_changed or not selector_patch:
        raise RuntimeError("Could not append actor numberplate variant from selected placements")

    new_actor_fields = list(actor_fields)
    new_actor_fields[payload_index] = ProtoField(103, 2, build_fields(patched_payload_fields))
    return build_fields(new_actor_fields), {
        "numberplate_index": new_plate_index_1based - 1,
        "numberplate_index_1based": new_plate_index_1based,
        "selector": selector_patch,
        "font_path": font_path or "",
        "background_path": background_path or "",
        "text_color": text_color,
        "textColor": text_color,
        "created_by": "LiveryLab",
        "owns_actor_numberplate": True,
        "placement_sources": placement_sources,
    }


def _actor_placement_numberplate_index_1based(raw: bytes) -> int:
    for field in parse_fields(raw):
        if field.number == 2 and field.wire == 0:
            return int(field.value)
    return 1


def append_actor_numberplate_pair_variant_data(
    actor_data: bytes,
    selector: dict[int, int] | None = None,
    font_path: str | None = None,
    left_background_path: str | None = None,
    right_background_path: str | None = None,
    text_color: list[float] | None = None,
    placement_calibrations_by_surface: dict[int, dict[str, Any]] | None = None,
) -> tuple[bytes, dict[str, Any]]:
    actor_fields, payload_index, payload_fields = _actor_payload_fields_for_update(actor_data)
    numberplate_fields = [field for field in payload_fields if field.number == 51 and field.wire == 2]
    if not numberplate_fields:
        raise RuntimeError("Actor has no numberplate definition to clone")

    selected_raw = _actor_selected_placement_raw_by_surface(actor_data, selector or {})
    selected_records = _actor_selected_placement_records_by_surface(actor_data, selector or {})
    left_source_raw = selected_raw.get(3)
    right_source_raw = selected_raw.get(4)
    if not left_source_raw or not right_source_raw:
        raise RuntimeError("Actor has no left/right door numberplate placements to clone")

    left_source_index = max(0, min(_actor_placement_numberplate_index_1based(left_source_raw) - 1, len(numberplate_fields) - 1))
    right_source_index = max(0, min(_actor_placement_numberplate_index_1based(right_source_raw) - 1, len(numberplate_fields) - 1))
    left_plate_index_1based = len(numberplate_fields) + 1
    right_plate_index_1based = len(numberplate_fields) + 2
    left_plate = _actor_clone_numberplate_entry(
        bytes(numberplate_fields[left_source_index].value),
        font_path=font_path,
        background_path=left_background_path,
        text_color=text_color,
    )
    right_plate = _actor_clone_numberplate_entry(
        bytes(numberplate_fields[right_source_index].value),
        font_path=font_path,
        background_path=right_background_path,
        text_color=text_color,
    )

    new_payload_fields: list[ProtoField] = []
    inserted_numberplates = False
    for field in payload_fields:
        new_payload_fields.append(field)
        if not inserted_numberplates and field.number == 51 and field.wire == 2 and field is numberplate_fields[-1]:
            new_payload_fields.append(ProtoField(51, 2, left_plate))
            new_payload_fields.append(ProtoField(51, 2, right_plate))
            inserted_numberplates = True

    selector_patch: dict[int, int] = {}
    placement_sources: dict[str, dict[str, Any]] = {}
    patched_payload_fields: list[ProtoField] = []
    placement_changed = False
    for field in new_payload_fields:
        if field.number != 52 or field.wire != 2:
            patched_payload_fields.append(field)
            continue
        placement_fields = parse_fields(field.value)
        counts: dict[int, int] = {}
        for placement_field in placement_fields:
            if placement_field.wire == 2:
                counts[int(placement_field.number)] = counts.get(int(placement_field.number), 0) + 1
        left_ordinal = counts.get(3, 0) + 1
        right_ordinal = counts.get(4, 0) + 1
        selector_patch = {3: left_ordinal, 4: right_ordinal}
        target_index = sum(1 for placement_field in placement_fields if placement_field.wire == 2)
        placement_sources[str(target_index)] = _numberplate_placement_source_info(selected_records.get(3, {}), 3)
        placement_sources[str(target_index + 1)] = _numberplate_placement_source_info(selected_records.get(4, {}), 4)
        patched_payload_fields.append(
            ProtoField(
                52,
                2,
                build_fields(
                    placement_fields
                    + [
                        ProtoField(
                            3,
                            2,
                            _actor_clone_placement_entry_for_surface(
                                left_source_raw,
                                left_plate_index_1based,
                                3,
                                (placement_calibrations_by_surface or {}).get(3),
                            ),
                        ),
                        ProtoField(
                            4,
                            2,
                            _actor_clone_placement_entry_for_surface(
                                right_source_raw,
                                right_plate_index_1based,
                                4,
                                (placement_calibrations_by_surface or {}).get(4),
                            ),
                        ),
                    ]
                ),
            )
        )
        placement_changed = True

    if not inserted_numberplates or not placement_changed or not selector_patch:
        raise RuntimeError("Could not append actor split numberplate variant")

    new_actor_fields = list(actor_fields)
    new_actor_fields[payload_index] = ProtoField(103, 2, build_fields(patched_payload_fields))
    return build_fields(new_actor_fields), {
        "mode": "split",
        "numberplate_indices": [left_plate_index_1based - 1, right_plate_index_1based - 1],
        "numberplate_indices_1based": [left_plate_index_1based, right_plate_index_1based],
        "left_numberplate_index": left_plate_index_1based - 1,
        "left_numberplate_index_1based": left_plate_index_1based,
        "right_numberplate_index": right_plate_index_1based - 1,
        "right_numberplate_index_1based": right_plate_index_1based,
        "selector": selector_patch,
        "font_path": font_path or "",
        "left_background_path": left_background_path or "",
        "right_background_path": right_background_path or "",
        "text_color": text_color,
        "textColor": text_color,
        "created_by": "LiveryLab",
        "owns_actor_numberplate": True,
        "placement_sources": placement_sources,
    }


def append_actor_numberplate_surface_variant_data(
    actor_data: bytes,
    selector: dict[int, int] | None = None,
    font_path: str | None = None,
    hood_background_path: str | None = None,
    left_background_path: str | None = None,
    right_background_path: str | None = None,
    text_color: list[float] | None = None,
    text_fields_by_surface: dict[int, dict[str, Any]] | None = None,
    placement_calibrations_by_surface: dict[int, dict[str, Any]] | None = None,
    target_surfaces: set[int] | list[int] | tuple[int, ...] | None = None,
) -> tuple[bytes, dict[str, Any]]:
    actor_fields, payload_index, payload_fields = _actor_payload_fields_for_update(actor_data)
    numberplate_fields = [field for field in payload_fields if field.number == 51 and field.wire == 2]
    if not numberplate_fields:
        raise RuntimeError("Actor has no numberplate definition to clone")

    selected_raw = _actor_selected_placement_raw_by_surface(actor_data, selector or {})
    selected_records = _actor_selected_placement_records_by_surface(actor_data, selector or {})
    surface_specs = [
        (1, "hood", hood_background_path),
        (3, "left", left_background_path),
        (4, "right", right_background_path),
    ]
    target_surface_set = {int(surface) for surface in target_surfaces} if target_surfaces is not None else set()
    clone_specs: list[dict[str, Any]] = []
    synthetic_surfaces: dict[str, dict[str, Any]] = {}
    skipped_synthetic_surfaces: dict[str, str] = {}
    for surface, label, background_path in surface_specs:
        if target_surfaces is not None and int(surface) not in target_surface_set:
            continue
        source_raw = selected_raw.get(surface)
        source_surface = surface
        source_record = selected_records.get(surface, {})
        synthetic = False
        if not source_raw and int(surface) == 1 and background_path:
            hood_calibration = (placement_calibrations_by_surface or {}).get(1)
            if isinstance(hood_calibration, dict) and isinstance(hood_calibration.get("actorBaseline"), dict):
                for candidate_surface in (3, 4):
                    candidate_raw = selected_raw.get(candidate_surface)
                    if candidate_raw:
                        source_raw = candidate_raw
                        source_surface = candidate_surface
                        source_record = selected_records.get(candidate_surface, {})
                        synthetic = True
                        break
                if not source_raw:
                    skipped_synthetic_surfaces[str(surface)] = "no door numberplate placement available as source"
            else:
                skipped_synthetic_surfaces[str(surface)] = "missing HOOD actorBaseline"
        if not source_raw:
            continue
        source_index = max(0, min(_actor_placement_numberplate_index_1based(source_raw) - 1, len(numberplate_fields) - 1))
        plate_index_1based = len(numberplate_fields) + len(clone_specs) + 1
        clone_specs.append(
            {
                "surface": int(surface),
                "label": str(label),
                "background_path": background_path,
                "source_index": source_index,
                "plate_index_1based": plate_index_1based,
                "source_raw": source_raw,
                "source_surface": int(source_surface),
                "source_record": source_record,
                "synthetic": synthetic,
            }
        )
        if synthetic:
            synthetic_surfaces[str(surface)] = {
                "surface": int(surface),
                "label": "HOOD",
                "source_surface": int(source_surface),
                "source_index": source_record.get("index"),
                "source_label": source_record.get("label"),
            }
    if not clone_specs:
        raise RuntimeError("Actor has no hood/left/right numberplate placements to clone")

    cloned_numberplates = [
        ProtoField(
            51,
            2,
            _actor_clone_numberplate_entry(
                bytes(numberplate_fields[int(spec["source_index"])].value),
                font_path=font_path,
                background_path=spec.get("background_path"),
                text_color=text_color,
                text_fields=(text_fields_by_surface or {}).get(int(spec["surface"])),
            ),
        )
        for spec in clone_specs
    ]

    new_payload_fields: list[ProtoField] = []
    inserted_numberplates = False
    for field in payload_fields:
        new_payload_fields.append(field)
        if not inserted_numberplates and field.number == 51 and field.wire == 2 and field is numberplate_fields[-1]:
            new_payload_fields.extend(cloned_numberplates)
            inserted_numberplates = True

    selector_patch: dict[int, int] = {}
    placement_sources: dict[str, dict[str, Any]] = {}
    patched_payload_fields: list[ProtoField] = []
    placement_changed = False
    for field in new_payload_fields:
        if field.number != 52 or field.wire != 2:
            patched_payload_fields.append(field)
            continue
        placement_fields = parse_fields(field.value)
        counts: dict[int, int] = {}
        for placement_field in placement_fields:
            if placement_field.wire == 2:
                counts[int(placement_field.number)] = counts.get(int(placement_field.number), 0) + 1
        clones = []
        target_index = sum(1 for placement_field in placement_fields if placement_field.wire == 2)
        for spec in clone_specs:
            surface = int(spec["surface"])
            plate_index_1based = int(spec["plate_index_1based"])
            if spec.get("synthetic") and surface == 1:
                placement_value = _actor_synthetic_hood_placement_entry(
                    plate_index_1based,
                    (placement_calibrations_by_surface or {}).get(1) or {},
                )
            else:
                placement_value = _actor_clone_placement_entry_for_surface(
                    bytes(spec["source_raw"]),
                    plate_index_1based,
                    surface,
                    (placement_calibrations_by_surface or {}).get(surface),
                )
            clones.append(
                ProtoField(
                    surface,
                    2,
                    placement_value,
                )
            )
            selector_patch[surface] = counts.get(surface, 0) + 1
            source_info = _numberplate_placement_source_info(
                spec.get("source_record") if isinstance(spec.get("source_record"), dict) else {},
                int(spec.get("source_surface") or surface),
            )
            if spec.get("synthetic"):
                source_info.update(
                    {
                        "synthetic": True,
                        "generated_surface": surface,
                        "generated_label": "HOOD",
                    }
                )
            placement_sources[str(target_index)] = source_info
            target_index += 1
        patched_payload_fields.append(ProtoField(52, 2, build_fields(placement_fields + clones)))
        placement_changed = True

    if not inserted_numberplates or not placement_changed or not selector_patch:
        raise RuntimeError("Could not append actor surface numberplate variant")

    indices_by_label = {str(spec["label"]): int(spec["plate_index_1based"]) - 1 for spec in clone_specs}
    indices_1based_by_label = {str(spec["label"]): int(spec["plate_index_1based"]) for spec in clone_specs}
    selected_by_surface = {int(spec["surface"]): int(spec["plate_index_1based"]) - 1 for spec in clone_specs}
    numberplate_indices = [int(spec["plate_index_1based"]) - 1 for spec in clone_specs]
    new_actor_fields = list(actor_fields)
    new_actor_fields[payload_index] = ProtoField(103, 2, build_fields(patched_payload_fields))
    return build_fields(new_actor_fields), {
        "mode": "surface",
        "numberplate_indices": numberplate_indices,
        "numberplate_indices_1based": [value + 1 for value in numberplate_indices],
        "hood_numberplate_index": indices_by_label.get("hood"),
        "hood_numberplate_index_1based": indices_1based_by_label.get("hood"),
        "left_numberplate_index": indices_by_label.get("left"),
        "left_numberplate_index_1based": indices_1based_by_label.get("left"),
        "right_numberplate_index": indices_by_label.get("right"),
        "right_numberplate_index_1based": indices_1based_by_label.get("right"),
        "selector": selector_patch,
        "selected_by_surface": selected_by_surface,
        "font_path": font_path or "",
        "hood_background_path": hood_background_path or "",
        "left_background_path": left_background_path or "",
        "right_background_path": right_background_path or "",
        "text_color": text_color,
        "textColor": text_color,
        "created_by": "LiveryLab",
        "owns_actor_numberplate": True,
        "placement_sources": placement_sources,
        "synthetic_surfaces": synthetic_surfaces,
        "skipped_synthetic_surfaces": skipped_synthetic_surfaces,
    }


def _actor_payload_fields_for_update(actor_data: bytes) -> tuple[list[ProtoField], int, list[ProtoField]]:
    actor_fields = parse_fields(actor_data)
    for index, field in enumerate(actor_fields):
        if field.number == 103 and field.wire == 2:
            return actor_fields, index, parse_fields(field.value)
    raise RuntimeError("Actor payload field 103 not found")


def apply_visual_numberplate_variant(actor_path: str | Path, visual_path: str | Path, font_path: str | None = None, background_path: str | None = None, text_color: list[float] | None = None, writer: GameFileWriter | None = None) -> dict[str, Any]:
    actor = Path(actor_path)
    visual = Path(visual_path)
    font = _normalize_actor_resource_input(actor, font_path)
    background = _normalize_actor_resource_input(actor, background_path)
    if not actor.exists():
        raise FileNotFoundError(f"Actor not found: {actor}")
    if not visual.exists():
        raise FileNotFoundError(f"Visual preset not found: {visual}")

    actor_data = actor.read_bytes()
    visual_data = visual.read_bytes()
    missing_before = validate_actor_numberplate_resources(actor, actor_data)
    selector = extract_visual_numberplate_selector(visual_data)
    placement_calibrations_by_surface = _numberplate_placement_calibrations_for_actor(actor)
    selected_index = _actor_selected_numberplate_index(actor_data, selector)
    actor_backup = None
    visual_backup = None
    actor_changed = False
    visual_changed = False
    created_new_variant = False
    appended_variant: dict[str, Any] = {}
    variant_info: dict[str, Any]

    if selected_index is not None and selected_index > 0 and any(int(value) > 1 for value in selector.values()):
        update_payload = {"index": selected_index}
        if font is not None:
            update_payload["font_path"] = font
        if background is not None:
            update_payload["background_path"] = background
        if text_color is not None:
            update_payload["overlay"] = text_color
        selected_raw = _actor_selected_placement_raw_by_surface(actor_data, selector)
        selected_records = _actor_selected_placement_records_by_surface(actor_data, selector)
        placement_updates = _numberplate_calibrated_placement_updates(
            selected_records,
            tuple(surface for surface in (1, 3, 4) if selected_raw.get(surface)),
            placement_calibrations_by_surface,
        )
        new_actor_data, actor_changed = update_actor_numberplate_data(actor_data, numberplates=[update_payload], placements=placement_updates)
        new_missing = _new_actor_numberplate_resource_errors(actor, missing_before, new_actor_data)
        if new_missing:
            raise RuntimeError(
                "Numberplate update would leave missing resources in the actor:\n"
                + "\n".join(f"- {row}" for row in new_missing)
        )
        if actor_changed:
            actor_backup = _write_game_bytes(actor, new_actor_data, writer, label="actor numberplate variant")
        variant_info = inspect_visual_numberplate_variant(actor, visual)
        variant_info["updated_existing"] = True
    else:
        new_actor_data, variant_info = append_actor_numberplate_variant_from_selected_data(
            actor_data,
            selector=selector,
            font_path=font,
            background_path=background,
            text_color=text_color,
            placement_calibrations_by_surface=placement_calibrations_by_surface,
        )
        appended_variant = dict(variant_info)
        new_missing = _new_actor_numberplate_resource_errors(actor, missing_before, new_actor_data)
        if new_missing:
            raise RuntimeError(
                "Numberplate variant would reference missing resources:\n"
                + "\n".join(f"- {row}" for row in new_missing)
            )
        actor_backup = _write_game_bytes(actor, new_actor_data, writer, label="actor numberplate variant")
        actor_changed = True
        visual_changed, visual_backup = update_visual_numberplate_selector_file(visual, variant_info["selector"], writer=writer)
        variant_info["updated_existing"] = False
        created_new_variant = True

    if not visual_changed and selected_index is not None and selected_index > 0:
        visual_backup = None
    variant_info = inspect_visual_numberplate_variant(actor, visual)
    if created_new_variant:
        variant_info["updated_existing"] = False
        variant_info["created_by"] = "LiveryLab"
        variant_info["owns_actor_numberplate"] = True
        if isinstance(appended_variant.get("placement_sources"), dict):
            variant_info["placement_sources"] = _json_ready(appended_variant.get("placement_sources"))
    else:
        variant_info["updated_existing"] = True
        variant_info["owns_actor_numberplate"] = False
    return {
        "actor_changed": actor_changed,
        "actor_backup": actor_backup,
        "visual_changed": visual_changed,
        "visual_backup": visual_backup,
        "variant": variant_info,
    }


def apply_visual_numberplate_pair_variant(
    actor_path: str | Path,
    visual_path: str | Path,
    font_path: str | None = None,
    left_background_path: str | None = None,
    right_background_path: str | None = None,
    text_color: list[float] | None = None,
    writer: GameFileWriter | None = None,
) -> dict[str, Any]:
    actor = Path(actor_path)
    visual = Path(visual_path)
    font = _normalize_actor_resource_input(actor, font_path)
    left_background = _normalize_actor_resource_input(actor, left_background_path)
    right_background = _normalize_actor_resource_input(actor, right_background_path)
    if not actor.exists():
        raise FileNotFoundError(f"Actor not found: {actor}")
    if not visual.exists():
        raise FileNotFoundError(f"Visual preset not found: {visual}")
    if not left_background and not right_background and not font and text_color is None:
        raise ValueError("Choose a font, color or at least one left/right numberplate background")

    actor_data = actor.read_bytes()
    visual_data = visual.read_bytes()
    missing_before = validate_actor_numberplate_resources(actor, actor_data)

    selector = extract_visual_numberplate_selector(visual_data)
    placement_calibrations_by_surface = _numberplate_placement_calibrations_for_actor(actor)
    selected_by_surface = _actor_selected_numberplate_indices_by_surface(actor_data, selector)
    left_index = selected_by_surface.get(3)
    right_index = selected_by_surface.get(4)
    can_update_existing_pair = (
        left_index is not None
        and right_index is not None
        and left_index != right_index
        and left_index > 0
        and right_index > 0
        and any(int(value) > 1 for value in selector.values())
    )

    actor_backup = None
    visual_backup = None
    actor_changed = False
    visual_changed = False
    created_new_variant = False
    appended_variant: dict[str, Any] = {}

    if can_update_existing_pair:
        updates: list[dict[str, Any]] = []
        left_update: dict[str, Any] = {"index": int(left_index)}
        right_update: dict[str, Any] = {"index": int(right_index)}
        if font is not None:
            left_update["font_path"] = font
            right_update["font_path"] = font
        if left_background is not None:
            left_update["background_path"] = left_background
        if right_background is not None:
            right_update["background_path"] = right_background
        if text_color is not None:
            left_update["overlay"] = text_color
            right_update["overlay"] = text_color
        updates.append(left_update)
        updates.append(right_update)
        selected_records = _actor_selected_placement_records_by_surface(actor_data, selector)
        placement_updates = _numberplate_calibrated_placement_updates(
            selected_records,
            (3, 4),
            placement_calibrations_by_surface,
        )
        new_actor_data, actor_changed = update_actor_numberplate_data(actor_data, numberplates=updates, placements=placement_updates)
        new_missing = _new_actor_numberplate_resource_errors(actor, missing_before, new_actor_data)
        if new_missing:
            raise RuntimeError(
                "Split numberplate update would leave missing resources in the actor:\n"
                + "\n".join(f"- {row}" for row in new_missing)
            )
        if actor_changed:
            actor_backup = _write_game_bytes(actor, new_actor_data, writer, label="actor split numberplate variant")
    else:
        new_actor_data, variant_info = append_actor_numberplate_pair_variant_data(
            actor_data,
            selector=selector,
            font_path=font,
            left_background_path=left_background,
            right_background_path=right_background,
            text_color=text_color,
            placement_calibrations_by_surface=placement_calibrations_by_surface,
        )
        appended_variant = dict(variant_info)
        new_missing = _new_actor_numberplate_resource_errors(actor, missing_before, new_actor_data)
        if new_missing:
            raise RuntimeError(
                "Split numberplate variant would reference missing resources:\n"
                + "\n".join(f"- {row}" for row in new_missing)
            )
        actor_backup = _write_game_bytes(actor, new_actor_data, writer, label="actor split numberplate variant")
        actor_changed = True
        visual_changed, visual_backup = update_visual_numberplate_selector_file(visual, variant_info["selector"], writer=writer)
        created_new_variant = True

    variant_info = inspect_visual_numberplate_variant(actor, visual)
    variant_info["mode"] = "split"
    variant_info["updated_existing"] = not created_new_variant
    variant_info["created_by"] = "LiveryLab"
    variant_info["owns_actor_numberplate"] = True
    if text_fields_by_surface:
        variant_info["game_text_fields_by_surface"] = _json_ready(text_fields_by_surface)
    if created_new_variant and isinstance(appended_variant.get("placement_sources"), dict):
        variant_info["placement_sources"] = _json_ready(appended_variant.get("placement_sources"))
    return {
        "actor_changed": actor_changed,
        "actor_backup": actor_backup,
        "visual_changed": visual_changed,
        "visual_backup": visual_backup,
        "variant": variant_info,
    }


def apply_visual_numberplate_surface_variant(
    actor_path: str | Path,
    visual_path: str | Path,
    font_path: str | None = None,
    hood_background_path: str | None = None,
    left_background_path: str | None = None,
    right_background_path: str | None = None,
    text_color: list[float] | None = None,
    text_fields_by_surface: dict[int, dict[str, Any]] | None = None,
    writer: GameFileWriter | None = None,
) -> dict[str, Any]:
    actor = Path(actor_path)
    visual = Path(visual_path)
    font = _normalize_actor_resource_input(actor, font_path)
    hood_background = _normalize_actor_resource_input(actor, hood_background_path)
    left_background = _normalize_actor_resource_input(actor, left_background_path)
    right_background = _normalize_actor_resource_input(actor, right_background_path)
    if not actor.exists():
        raise FileNotFoundError(f"Actor not found: {actor}")
    if not visual.exists():
        raise FileNotFoundError(f"Visual preset not found: {visual}")
    if not hood_background and not left_background and not right_background and not font and text_color is None:
        raise ValueError("Choose a font, color or at least one hood/left/right numberplate background")

    actor_data = actor.read_bytes()
    visual_data = visual.read_bytes()
    missing_before = validate_actor_numberplate_resources(actor, actor_data)

    selector = extract_visual_numberplate_selector(visual_data)
    placement_calibrations_by_surface = _numberplate_placement_calibrations_for_actor(actor)
    selected_by_surface = _actor_selected_numberplate_indices_by_surface(actor_data, selector)
    selected_raw = _actor_selected_placement_raw_by_surface(actor_data, selector)
    available_surfaces = [surface for surface in (1, 3, 4) if selected_raw.get(surface)]
    can_update_existing_surface = (
        bool(available_surfaces)
        and all(selected_by_surface.get(surface) is not None and int(selected_by_surface.get(surface) or 0) > 0 for surface in available_surfaces)
        and len({int(selected_by_surface.get(surface) or -1) for surface in available_surfaces}) == len(available_surfaces)
        and any(int(value) > 1 for value in selector.values())
    )

    actor_backup = None
    visual_backup = None
    actor_changed = False
    visual_changed = False
    created_new_variant = False
    appended_variant: dict[str, Any] = {}

    if can_update_existing_surface:
        background_by_surface = {
            1: hood_background,
            3: left_background,
            4: right_background,
        }
        updates: list[dict[str, Any]] = []
        for surface in available_surfaces:
            plate_index = selected_by_surface.get(surface)
            if plate_index is None:
                continue
            update_payload: dict[str, Any] = {"index": int(plate_index)}
            if font is not None:
                update_payload["font_path"] = font
            background = background_by_surface.get(surface)
            if background is not None:
                update_payload["background_path"] = background
            if text_color is not None:
                update_payload["overlay"] = text_color
            text_fields = (text_fields_by_surface or {}).get(int(surface))
            if text_fields:
                update_payload["text_fields"] = text_fields
            updates.append(update_payload)
        selected_records = _actor_selected_placement_records_by_surface(actor_data, selector)
        placement_updates = _numberplate_calibrated_placement_updates(
            selected_records,
            tuple(available_surfaces),
            placement_calibrations_by_surface,
        )
        new_actor_data, actor_changed = update_actor_numberplate_data(actor_data, numberplates=updates, placements=placement_updates)
        hood_calibration = (placement_calibrations_by_surface or {}).get(1)
        append_missing_hood = (
            hood_background is not None
            and not selected_raw.get(1)
            and any(selected_raw.get(surface) for surface in (3, 4))
            and isinstance(hood_calibration, dict)
            and isinstance(hood_calibration.get("actorBaseline"), dict)
        )
        if append_missing_hood:
            new_actor_data, hood_variant_info = append_actor_numberplate_surface_variant_data(
                new_actor_data,
                selector=selector,
                font_path=font,
                hood_background_path=hood_background,
                left_background_path=None,
                right_background_path=None,
                text_color=text_color,
                text_fields_by_surface=text_fields_by_surface,
                placement_calibrations_by_surface=placement_calibrations_by_surface,
                target_surfaces={1},
            )
            appended_variant = dict(hood_variant_info)
            next_selector = dict(selector)
            next_selector.update(hood_variant_info.get("selector") or {})
            visual_changed, visual_backup = update_visual_numberplate_selector_file(visual, next_selector, writer=writer)
            actor_changed = True
            created_new_variant = True
        new_missing = _new_actor_numberplate_resource_errors(actor, missing_before, new_actor_data)
        if new_missing:
            raise RuntimeError(
                "Surface numberplate update would leave missing resources in the actor:\n"
                + "\n".join(f"- {row}" for row in new_missing)
            )
        if actor_changed:
            actor_backup = _write_game_bytes(actor, new_actor_data, writer, label="actor surface numberplate variant")
    else:
        new_actor_data, variant_info = append_actor_numberplate_surface_variant_data(
            actor_data,
            selector=selector,
            font_path=font,
            hood_background_path=hood_background,
            left_background_path=left_background,
            right_background_path=right_background,
            text_color=text_color,
            text_fields_by_surface=text_fields_by_surface,
            placement_calibrations_by_surface=placement_calibrations_by_surface,
        )
        appended_variant = dict(variant_info)
        new_missing = _new_actor_numberplate_resource_errors(actor, missing_before, new_actor_data)
        if new_missing:
            raise RuntimeError(
                "Surface numberplate variant would reference missing resources:\n"
                + "\n".join(f"- {row}" for row in new_missing)
            )
        actor_backup = _write_game_bytes(actor, new_actor_data, writer, label="actor surface numberplate variant")
        actor_changed = True
        visual_changed, visual_backup = update_visual_numberplate_selector_file(visual, variant_info["selector"], writer=writer)
        created_new_variant = True

    variant_info = inspect_visual_numberplate_variant(actor, visual)
    variant_info["mode"] = "surface"
    variant_info["updated_existing"] = not created_new_variant
    variant_info["created_by"] = "LiveryLab"
    variant_info["owns_actor_numberplate"] = True
    if created_new_variant and isinstance(appended_variant.get("placement_sources"), dict):
        variant_info["placement_sources"] = _json_ready(appended_variant.get("placement_sources"))
    if created_new_variant:
        for key in ("synthetic_surfaces", "skipped_synthetic_surfaces"):
            if isinstance(appended_variant.get(key), dict) and appended_variant.get(key):
                variant_info[key] = _json_ready(appended_variant.get(key))
    return {
        "actor_changed": actor_changed,
        "actor_backup": actor_backup,
        "visual_changed": visual_changed,
        "visual_backup": visual_backup,
        "variant": variant_info,
    }


def _actor_set_or_append_field(fields: list[ProtoField], number: int, wire: int, value: Any) -> list[ProtoField]:
    updated = False
    new_fields: list[ProtoField] = []
    for field in fields:
        if not updated and field.number == number and field.wire == wire:
            new_fields.append(ProtoField(number, wire, value))
            updated = True
        else:
            new_fields.append(field)
    if not updated:
        new_fields.append(ProtoField(number, wire, value))
    return new_fields


def _actor_update_float_vector_message(raw: bytes, values: list[Any] | tuple[Any, ...], size: int) -> bytes:
    fields = parse_fields(raw) if raw else []
    for index in range(size):
        value = values[index] if index < len(values) else None
        if value is None or str(value).strip() == "":
            continue
        packed = struct.pack("<f", float(value))
        fields = _actor_set_or_append_field(fields, index + 1, 5, packed)
    return build_fields(fields)


def _actor_update_numberplate_entry_bytes(raw: bytes, payload: dict[str, Any]) -> bytes:
    fields = parse_fields(raw)
    if payload.get("font_path") is not None:
        fields = _actor_set_or_append_field(fields, 4, 2, norm_slashes(str(payload.get("font_path", "")).strip()).encode("utf-8"))
    if payload.get("background_path") is not None:
        background_path = norm_slashes(str(payload.get("background_path", "")).strip())
        if background_path:
            fields = _actor_set_or_append_field(fields, 5, 2, background_path.encode("utf-8"))
        else:
            fields = [field for field in fields if not (field.number == 5 and field.wire == 2)]
    if payload.get("opacity") not in (None, ""):
        fields = _actor_set_or_append_field(fields, 3, 5, struct.pack("<f", float(payload.get("opacity"))))

    overlay = payload.get("overlay")
    if isinstance(overlay, (list, tuple)):
        # Field 7 is the number/font tint in linear RGBA, mirrored in the UI as textColor.
        current = next((field for field in fields if field.number == 7 and field.wire == 2), None)
        updated = _actor_update_float_vector_message(bytes(current.value) if current and current.wire == 2 else b"", list(overlay), 4)
        fields = _actor_set_or_append_field(fields, 7, 2, updated)

    text_fields = _coerce_numberplate_actor_text_fields(payload.get("text_fields"))
    if text_fields is not None:
        fields = [field for field in fields if field.number not in NUMBERPLATE_ACTOR_TEXT_FIELD_NUMBERS]
        for key in ("value_8", "value_9", "value_10", "value_11"):
            value = text_fields.get(key)
            if value not in (None, ""):
                fields = _actor_set_or_append_field(fields, int(key.rsplit("_", 1)[-1]), 0, int(value))
        if text_fields.get("value_15") not in (None, ""):
            fields = _actor_set_or_append_field(fields, 15, 5, struct.pack("<f", float(text_fields.get("value_15"))))
        if text_fields.get("value_16") not in (None, ""):
            fields = _actor_set_or_append_field(fields, 16, 0, _actor_encode_signed64_varint(int(text_fields.get("value_16"))))

    for field_number in (8, 9, 10, 11):
        value = payload.get(f"value_{field_number}")
        if value in (None, ""):
            continue
        fields = _actor_set_or_append_field(fields, field_number, 0, int(value))
    if payload.get("value_15") not in (None, ""):
        fields = _actor_set_or_append_field(fields, 15, 5, struct.pack("<f", float(payload.get("value_15"))))
    if payload.get("value_16") not in (None, ""):
        fields = _actor_set_or_append_field(fields, 16, 0, _actor_encode_signed64_varint(int(payload.get("value_16"))))
    return build_fields(fields)


def _actor_update_placement_entry_bytes(raw: bytes, payload: dict[str, Any]) -> bytes:
    fields = parse_fields(raw)
    if payload.get("field_1") not in (None, ""):
        fields = _actor_set_or_append_field(fields, 1, 0, int(payload.get("field_1")))
    if payload.get("field_2") not in (None, ""):
        fields = _actor_set_or_append_field(fields, 2, 0, int(payload.get("field_2")))
    if payload.get("label") is not None:
        fields = _actor_set_or_append_field(fields, 3, 2, str(payload.get("label", "")).strip().encode("utf-8"))
    for field_number, key, size in ((4, "position", 3), (5, "rotation", 3), (6, "scale", 3)):
        values = payload.get(key)
        if isinstance(values, (list, tuple)):
            current = next((field for field in fields if field.number == field_number and field.wire == 2), None)
            updated = _actor_update_float_vector_message(bytes(current.value) if current and current.wire == 2 else b"", list(values), size)
            fields = _actor_set_or_append_field(fields, field_number, 2, updated)
    return build_fields(fields)


def update_actor_numberplate_data(actor_data: bytes, numberplates: list[dict[str, Any]] | None = None, placements: list[dict[str, Any]] | None = None) -> tuple[bytes, bool]:
    actor_fields = parse_fields(actor_data)
    changed = False
    new_actor_fields: list[ProtoField] = []
    numberplate_updates = {int(row.get("index")): row for row in (numberplates or []) if row is not None and str(row.get("index", "")).strip() != ""}
    placement_updates = {int(row.get("index")): row for row in (placements or []) if row is not None and str(row.get("index", "")).strip() != ""}
    for field in actor_fields:
        if field.number != 103 or field.wire != 2:
            new_actor_fields.append(field)
            continue
        payload_fields = parse_fields(field.value)
        new_payload_fields: list[ProtoField] = []
        numberplate_index = 0
        for payload_field in payload_fields:
            if payload_field.number == 51 and payload_field.wire == 2:
                update = numberplate_updates.get(numberplate_index)
                if update is not None:
                    updated_value = _actor_update_numberplate_entry_bytes(bytes(payload_field.value), update)
                    if updated_value != bytes(payload_field.value):
                        changed = True
                        new_payload_fields.append(ProtoField(payload_field.number, payload_field.wire, updated_value))
                    else:
                        new_payload_fields.append(payload_field)
                else:
                    new_payload_fields.append(payload_field)
                numberplate_index += 1
                continue
            if payload_field.number == 52 and payload_field.wire == 2 and placement_updates:
                placement_fields = parse_fields(payload_field.value)
                new_placement_fields: list[ProtoField] = []
                placement_index = 0
                placement_changed = False
                for placement_field in placement_fields:
                    if placement_field.wire == 2:
                        update = placement_updates.get(placement_index)
                        if update is not None:
                            updated_value = _actor_update_placement_entry_bytes(bytes(placement_field.value), update)
                            if updated_value != bytes(placement_field.value):
                                placement_changed = True
                                new_placement_fields.append(ProtoField(placement_field.number, placement_field.wire, updated_value))
                            else:
                                new_placement_fields.append(placement_field)
                        else:
                            new_placement_fields.append(placement_field)
                        placement_index += 1
                    else:
                        new_placement_fields.append(placement_field)
                if placement_changed:
                    changed = True
                    new_payload_fields.append(ProtoField(payload_field.number, payload_field.wire, build_fields(new_placement_fields)))
                else:
                    new_payload_fields.append(payload_field)
                continue
            new_payload_fields.append(payload_field)
        new_actor_fields.append(ProtoField(field.number, field.wire, build_fields(new_payload_fields)))
    return (build_fields(new_actor_fields), True) if changed else (actor_data, False)


def update_actor_numberplate_file(
    actor_path: str | Path,
    numberplates: list[dict[str, Any]] | None = None,
    placements: list[dict[str, Any]] | None = None,
    writer: GameFileWriter | None = None,
) -> tuple[bool, Path | None]:
    path = Path(actor_path)
    old_data = path.read_bytes()
    new_data, changed = update_actor_numberplate_data(old_data, numberplates=numberplates, placements=placements)
    if not changed:
        return False, None
    backup = _write_game_bytes(path, new_data, writer, label="actor numberplate update")
    return True, backup


def _actor_numberplate_raws(actor_data: bytes) -> list[bytes]:
    _actor_fields, _payload_index, payload_fields = _actor_payload_fields_for_update(actor_data)
    return [bytes(field.value) for field in payload_fields if field.number == 51 and field.wire == 2]


def _actor_placement_label_from_raw(raw: bytes) -> str:
    for field in parse_fields(raw):
        if field.number == 3 and field.wire == 2:
            try:
                return bytes(field.value).decode("utf-8", errors="replace")
            except Exception:
                return ""
    return ""


def _actor_placement_records_from_payload(payload_fields: list[ProtoField]) -> list[dict[str, Any]]:
    records: list[dict[str, Any]] = []
    placement_index = 0
    for payload_field in payload_fields:
        if payload_field.number != 52 or payload_field.wire != 2:
            continue
        for placement_field in parse_fields(payload_field.value):
            if placement_field.wire != 2:
                continue
            raw = bytes(placement_field.value)
            records.append(
                {
                    "index": placement_index,
                    "surface": int(placement_field.number),
                    "numberplateIndex1": _actor_placement_numberplate_index_1based(raw),
                    "label": _actor_placement_label_from_raw(raw),
                    "raw": raw,
                }
            )
            placement_index += 1
    return records


def _actor_merge_owned_numberplate_data(game_actor_data: bytes, source_actor_data: bytes, owned_indices: list[int]) -> tuple[bytes, dict[str, Any]]:
    owned_zero = sorted({int(index) for index in owned_indices if int(index) >= 0})
    owned_one = {index + 1 for index in owned_zero}
    if not owned_zero:
        return game_actor_data, {"changed": False, "skipped": "skin does not own a custom numberplate variant"}

    actor_fields, payload_index, payload_fields = _actor_payload_fields_for_update(game_actor_data)
    _source_actor_fields, _source_payload_index, source_payload_fields = _actor_payload_fields_for_update(source_actor_data)
    source_numberplates = _actor_numberplate_raws(source_actor_data)
    source_placements = [
        row
        for row in _actor_placement_records_from_payload(source_payload_fields)
        if int(row.get("numberplateIndex1") or 0) in owned_one
    ]
    source_placements_by_index = {int(row["index"]): row for row in source_placements}
    source_placements_by_key: dict[tuple[int, int, str], dict[str, Any]] = {}
    for row in source_placements:
        key = (int(row["surface"]), int(row["numberplateIndex1"]), str(row.get("label") or ""))
        source_placements_by_key.setdefault(key, row)

    target_numberplate_count = sum(1 for field in payload_fields if field.number == 51 and field.wire == 2)
    append_numberplate_indices: list[int] = []
    warnings: list[str] = []
    missing_owned_indices = [index for index in owned_zero if index >= target_numberplate_count]
    if missing_owned_indices:
        last_required_index = max(missing_owned_indices)
        if last_required_index < len(source_numberplates):
            append_numberplate_indices = list(range(target_numberplate_count, last_required_index + 1))
            intermediate = [index for index in append_numberplate_indices if index not in owned_zero]
            if intermediate:
                warnings.append(
                    "appended intermediate numberplate definitions to preserve owned variant indices: "
                    + ", ".join(str(index) for index in intermediate)
                )
        else:
            warnings.append(f"numberplate index {last_required_index} cannot be appended because the local actor does not contain it")

    changed = False
    numberplate_updates = 0
    placement_updates = 0
    placement_appends = 0
    inserted_appended_numberplates = False
    new_payload_fields: list[ProtoField] = []
    numberplate_index = 0
    numberplate_fields_seen = 0
    for payload_field in payload_fields:
        if payload_field.number == 51 and payload_field.wire == 2:
            numberplate_fields_seen += 1
            if numberplate_index in owned_zero and numberplate_index < len(source_numberplates):
                source_raw = source_numberplates[numberplate_index]
                if source_raw != bytes(payload_field.value):
                    changed = True
                    numberplate_updates += 1
                    new_payload_fields.append(ProtoField(payload_field.number, payload_field.wire, source_raw))
                else:
                    new_payload_fields.append(payload_field)
            else:
                new_payload_fields.append(payload_field)
            if numberplate_fields_seen == target_numberplate_count and append_numberplate_indices:
                for append_index in append_numberplate_indices:
                    new_payload_fields.append(ProtoField(51, 2, source_numberplates[append_index]))
                    numberplate_updates += 1
                    changed = True
                inserted_appended_numberplates = True
            numberplate_index += 1
            continue

        if payload_field.number == 52 and payload_field.wire == 2 and source_placements:
            placement_fields = parse_fields(payload_field.value)
            target_records: list[dict[str, Any]] = []
            target_index = 0
            for placement_field in placement_fields:
                if placement_field.wire != 2:
                    continue
                raw = bytes(placement_field.value)
                target_records.append(
                    {
                        "index": target_index,
                        "surface": int(placement_field.number),
                        "numberplateIndex1": _actor_placement_numberplate_index_1based(raw),
                        "label": _actor_placement_label_from_raw(raw),
                    }
                )
                target_index += 1

            used_source_indices: set[int] = set()
            new_placement_fields: list[ProtoField] = []
            placement_index = 0
            for placement_field in placement_fields:
                if placement_field.wire != 2:
                    new_placement_fields.append(placement_field)
                    continue
                raw = bytes(placement_field.value)
                target_numberplate = _actor_placement_numberplate_index_1based(raw)
                target_label = _actor_placement_label_from_raw(raw)
                source = None
                same_index = source_placements_by_index.get(placement_index)
                if same_index and target_numberplate in owned_one:
                    source = same_index
                if source is None and target_numberplate in owned_one:
                    source = source_placements_by_key.get((int(placement_field.number), int(target_numberplate), target_label))
                if source is not None:
                    used_source_indices.add(int(source["index"]))
                    source_raw = bytes(source["raw"])
                    if source_raw != raw:
                        new_placement_fields.append(ProtoField(placement_field.number, placement_field.wire, source_raw))
                        placement_updates += 1
                        changed = True
                    else:
                        new_placement_fields.append(placement_field)
                else:
                    new_placement_fields.append(placement_field)
                placement_index += 1

            target_keys = {
                (int(row["surface"]), int(row["numberplateIndex1"]), str(row.get("label") or ""))
                for row in target_records
            }
            for source in source_placements:
                source_index = int(source["index"])
                source_key = (int(source["surface"]), int(source["numberplateIndex1"]), str(source.get("label") or ""))
                if source_index in used_source_indices or source_key in target_keys:
                    continue
                new_placement_fields.append(ProtoField(int(source["surface"]), 2, bytes(source["raw"])))
                placement_appends += 1
                changed = True
            new_payload_fields.append(ProtoField(payload_field.number, payload_field.wire, build_fields(new_placement_fields)))
            continue

        new_payload_fields.append(payload_field)

    if append_numberplate_indices and not inserted_appended_numberplates:
        warnings.append("actor numberplate container was not found for appending")

    if not changed:
        return game_actor_data, {
            "changed": False,
            "warnings": warnings,
            "numberplateUpdates": 0,
            "placementUpdates": 0,
            "placementAppends": 0,
        }

    new_actor_fields = list(actor_fields)
    new_actor_fields[payload_index] = ProtoField(103, 2, build_fields(new_payload_fields))
    return build_fields(new_actor_fields), {
        "changed": True,
        "warnings": warnings,
        "numberplateUpdates": numberplate_updates,
        "placementUpdates": placement_updates,
        "placementAppends": placement_appends,
    }


def _selector_for_owned_numberplate_variant(actor_data: bytes, variant: dict[str, Any], current_selector: dict[int, int]) -> tuple[dict[int, int], dict[str, Any]]:
    owned_indices = _numberplate_variant_indices(variant)
    owned_zero = set(owned_indices)
    owned_one = {index + 1 for index in owned_zero}
    selected_by_surface_raw = variant.get("selected_by_surface") if isinstance(variant.get("selected_by_surface"), dict) else {}
    desired_by_surface: dict[int, int] = {}
    for surface_raw, index_raw in selected_by_surface_raw.items():
        try:
            surface = int(surface_raw)
            index = int(index_raw)
        except Exception:
            continue
        if index in owned_zero:
            desired_by_surface[surface] = index + 1

    placements = _actor_extract_numberplate_placements(actor_data)
    counts: dict[int, int] = {}
    selector_updates: dict[int, int] = {}
    for placement in placements:
        try:
            surface = int(placement.get("outer_field_number") or placement.get("surface") or 0)
            numberplate_index_1based = int(placement.get("field_2") or placement.get("numberplate_index") or 0)
        except Exception:
            continue
        if not surface:
            continue
        counts[surface] = counts.get(surface, 0) + 1
        if surface in desired_by_surface and desired_by_surface[surface] == numberplate_index_1based:
            selector_updates[surface] = counts[surface]
        elif surface not in desired_by_surface and numberplate_index_1based in owned_one:
            selector_updates.setdefault(surface, counts[surface])

    next_selector = {int(surface): int(ordinal) for surface, ordinal in (current_selector or {}).items()}
    next_selector.update(selector_updates)
    return next_selector, {
        "changed": next_selector != current_selector,
        "selectorUpdates": selector_updates,
        "ownedNumberplateIndices": owned_indices,
    }


def _numberplate_sanitized_indices(values: Any) -> list[int]:
    if not isinstance(values, list):
        return []
    indices: set[int] = set()
    for value in values:
        try:
            index = int(value)
        except Exception:
            continue
        if index >= 0:
            indices.add(index)
    return sorted(indices)


def _numberplate_path_is_liverylab_generated(value: Any) -> bool:
    rel = normalize_game_rel_path(str(value or "").strip()).lower().replace("/", "\\")
    return "\\numberplates\\liverylab_baked\\" in f"\\{rel}"


def _sanitize_disabled_numberplate_variant_metadata(value: Any, start_number: Any = "") -> dict[str, Any]:
    return export_sanitize.clean_disabled_numberplate_variant_metadata(value, start_number)


def _numberplate_plate_is_liverylab_generated(plate: dict[str, Any] | None) -> bool:
    if not isinstance(plate, dict):
        return False
    return any(
        _numberplate_path_is_liverylab_generated(plate.get(key))
        for key in ("font_path", "background_path")
    )


def _numberplate_visibility_color(plate: dict[str, Any] | None, alpha: float) -> list[float]:
    color = _actor_numberplate_text_color(plate) or [1.0, 1.0, 1.0, 1.0]
    result = list(color[:4])
    while len(result) < 4:
        result.append(1.0)
    result[3] = max(0.0, min(1.0, float(alpha)))
    return result


def _hidden_numberplate_update(index: int, plate: dict[str, Any] | None) -> dict[str, Any]:
    return {"index": int(index), "opacity": 0.0, "overlay": _numberplate_visibility_color(plate, 0.0), "background_path": ""}


def _visible_numberplate_update_from_plate(index: int, plate: dict[str, Any] | None) -> dict[str, Any]:
    opacity = 1.0
    if isinstance(plate, dict) and plate.get("opacity") not in (None, ""):
        try:
            opacity = float(plate.get("opacity"))
        except Exception:
            opacity = 1.0
    if opacity <= 0.0:
        opacity = 1.0
    update: dict[str, Any] = {"index": int(index), "opacity": opacity, "overlay": _numberplate_visibility_color(plate, 1.0)}
    if isinstance(plate, dict):
        if plate.get("font_path") is not None:
            update["font_path"] = str(plate.get("font_path") or "")
        update["background_path"] = str(plate.get("background_path") or "")
    return update


def publish_local_numberplate_visibility_to_game_actor(entry: dict[str, Any], game_root: Path, writer: GameFileWriter) -> dict[str, Any]:
    variant = entry.get("numberplate_variant") if isinstance(entry.get("numberplate_variant"), dict) else {}
    source = str(variant.get("source_type") or variant.get("numberplate_source") or "").strip().lower()
    hidden_source = source in NUMBERPLATE_NONE_SOURCE_TYPES or variant.get("numberplate_hidden") is True
    if hidden_source and variant.get("owns_actor_numberplate") is not True:
        return {"published": False, "skipped": "AC EVO empty numberplate selector needs no actor changes"}
    if not hidden_source and source not in NUMBERPLATE_STOCK_SOURCE_TYPES:
        return {"published": False, "skipped": "numberplate source is not AC EVO default/no numberplate"}
    registry_indices = _numberplate_sanitized_indices(variant.get("visibility_indices"))
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    actor_rel = str(files.get("actor") or "").strip()
    visual_rel = str(files.get("visual_preset") or "").strip()
    game_actor_path = _disk_from_game_rel(game_root, actor_rel)
    local_actor_path = _local_skin_asset_path(entry, actor_rel)
    if game_actor_path is None or not game_actor_path.exists():
        return {"published": False, "skipped": "game actor file not found"}
    if local_actor_path is None or not local_actor_path.exists():
        return {"published": False, "skipped": "local actor file not found"}
    visual_path = _skin_file_disk_path(game_root, entry, visual_rel) if visual_rel else None
    visual_data = visual_path.read_bytes() if visual_path and visual_path.exists() else b""
    local_actor_data = local_actor_path.read_bytes()
    game_actor_data = game_actor_path.read_bytes()
    local_plates = _actor_extract_numberplates(local_actor_data)
    game_plates = _actor_extract_numberplates(game_actor_data)
    local_active_indices = _actor_selected_numberplate_indices(local_actor_data, visual_data)
    game_active_indices = _actor_selected_numberplate_indices(game_actor_data, visual_data)
    generated_indices = sorted(
        index
        for index, plate in enumerate(game_plates)
        if _numberplate_plate_is_liverylab_generated(plate)
    )

    restore_indices: set[int] = set()
    hidden_indices: set[int] = set()
    if hidden_source:
        hidden_indices.update(registry_indices)
        hidden_indices.update(local_active_indices)
        hidden_indices.update(game_active_indices)
        hidden_indices.update(generated_indices)
        hidden_indices.update(range(len(local_plates)))
    else:
        restore_indices.update(registry_indices)
        restore_indices.update(local_active_indices)
        hidden_indices.update(generated_indices)
        hidden_indices.update(index for index in game_active_indices if index not in restore_indices)
        hidden_indices.update(index for index in game_active_indices if index >= len(local_plates))
        hidden_indices.difference_update(restore_indices)

    updates_by_index: dict[int, dict[str, Any]] = {}
    for index in sorted(hidden_indices):
        if 0 <= index < len(game_plates):
            updates_by_index[index] = _hidden_numberplate_update(index, game_plates[index])
    for index in sorted(restore_indices):
        if 0 <= index < len(local_plates) and index < len(game_plates):
            updates_by_index[index] = _visible_numberplate_update_from_plate(index, local_plates[index])
    updates = [updates_by_index[index] for index in sorted(updates_by_index)]
    if not updates:
        return {"published": False, "skipped": "no local visibility payload found"}
    new_actor_data, actor_changed = update_actor_numberplate_data(game_actor_data, numberplates=updates)
    actor_backup = None
    if actor_changed:
        actor_backup = _write_game_bytes(game_actor_path, new_actor_data, writer, label="actor numberplate visibility publish")
    return {
        "published": actor_changed,
        "actorChanged": actor_changed,
        "actorPath": str(game_actor_path),
        "localActorPath": str(local_actor_path),
        "actorBackup": str(actor_backup) if actor_backup else "",
        "numberplateUpdates": len(updates),
        "visibilityIndices": sorted(set(registry_indices) | set(local_active_indices)),
        "hiddenIndices": sorted(hidden_indices),
        "restoredIndices": sorted(restore_indices),
        "generatedIndices": generated_indices,
    }


def publish_local_numberplate_variant_to_game_actor(entry: dict[str, Any], game_root: Path, writer: GameFileWriter) -> dict[str, Any]:
    variant = entry.get("numberplate_variant") if isinstance(entry.get("numberplate_variant"), dict) else {}
    if variant.get("owns_actor_numberplate") is not True:
        return {"published": False, "skipped": "skin has no LiveryLab-owned numberplate variant"}
    owned_indices = _numberplate_variant_indices(variant)
    if not owned_indices:
        return {"published": False, "skipped": "skin has no owned numberplate indices"}
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    actor_rel = str(files.get("actor") or "").strip()
    visual_rel = str(files.get("visual_preset") or "").strip()
    game_actor_path = _disk_from_game_rel(game_root, actor_rel)
    local_actor_path = _local_skin_asset_path(entry, actor_rel)
    if game_actor_path is None or not game_actor_path.exists():
        return {"published": False, "skipped": "game actor file not found"}
    if local_actor_path is None or not local_actor_path.exists():
        return {"published": False, "skipped": "local actor file not found"}

    game_actor_data = game_actor_path.read_bytes()
    local_actor_data = local_actor_path.read_bytes()
    new_actor_data, merge_info = _actor_merge_owned_numberplate_data(game_actor_data, local_actor_data, owned_indices)
    repair_info: dict[str, Any] = {"changed": False, "repairedResources": []}
    if merge_info.get("changed"):
        game_actor_data = new_actor_data
    repaired_actor_data, repair_info = _repair_actor_missing_numberplate_resources(game_actor_path, game_actor_data, protected_indices=owned_indices)
    if repair_info.get("changed"):
        game_actor_data = repaired_actor_data
    actor_backup = None
    if merge_info.get("changed") or repair_info.get("changed"):
        actor_backup = _write_game_bytes(game_actor_path, game_actor_data, writer, label="actor numberplate placement publish")

    selector_changed = False
    visual_backup = None
    local_visual_changed = False
    local_visual_backup = None
    selector_info: dict[str, Any] = {}
    visual_path = _disk_from_game_rel(game_root, visual_rel)
    if visual_path and visual_path.exists():
        current_selector = extract_visual_numberplate_selector(visual_path.read_bytes())
        next_selector, selector_info = _selector_for_owned_numberplate_variant(game_actor_data, variant, current_selector)
        selector_changed, visual_backup = update_visual_numberplate_selector_file(visual_path, next_selector, writer=writer)
        local_visual_path = _local_skin_asset_path(entry, visual_rel)
        if local_visual_path and local_visual_path.exists():
            try:
                same_visual = local_visual_path.resolve() == visual_path.resolve()
            except Exception:
                same_visual = str(local_visual_path).lower() == str(visual_path).lower()
            if not same_visual:
                local_visual_changed, local_visual_backup = update_visual_numberplate_selector_file(local_visual_path, next_selector)

    return {
        "published": bool(merge_info.get("changed") or repair_info.get("changed") or selector_changed),
        "actorChanged": bool(merge_info.get("changed")),
        "actorResourcesRepaired": bool(repair_info.get("changed")),
        "selectorChanged": selector_changed,
        "localSelectorChanged": local_visual_changed,
        "actorPath": str(game_actor_path),
        "localActorPath": str(local_actor_path),
        "actorBackup": str(actor_backup) if actor_backup else "",
        "visualPath": str(visual_path) if visual_path else "",
        "visualBackup": str(visual_backup) if visual_backup else "",
        "localVisualBackup": str(local_visual_backup) if local_visual_backup else "",
        "numberplateUpdates": int(merge_info.get("numberplateUpdates") or 0),
        "placementUpdates": int(merge_info.get("placementUpdates") or 0),
        "placementAppends": int(merge_info.get("placementAppends") or 0),
        "placementUpdatesTotal": int(merge_info.get("placementUpdates") or 0) + int(merge_info.get("placementAppends") or 0),
        "repairedResources": repair_info.get("repairedResources") or [],
        "warnings": merge_info.get("warnings") or [],
        "selector": selector_info.get("selectorUpdates") or {},
    }


def prune_actor_numberplate_variant_data(actor_data: bytes, numberplate_index: int) -> tuple[bytes, dict[str, Any]]:
    return prune_actor_numberplate_variants_data(actor_data, [numberplate_index])


def prune_actor_numberplate_variants_data(actor_data: bytes, numberplate_indices: list[int] | tuple[int, ...] | set[int]) -> tuple[bytes, dict[str, Any]]:
    actor_fields, payload_index, payload_fields = _actor_payload_fields_for_update(actor_data)
    numberplate_count = sum(1 for field in payload_fields if field.number == 51 and field.wire == 2)
    target_indices = sorted({int(value) for value in numberplate_indices if int(value) > 0})
    if not target_indices:
        return actor_data, {"changed": False, "skipped": "default numberplate is never pruned"}
    missing_targets = [index for index in target_indices if index >= numberplate_count]
    if missing_targets:
        return actor_data, {"changed": False, "skipped": "numberplate index no longer exists", "missingNumberplateIndices": missing_targets}
    target_indices_set = set(target_indices)
    target_indices_1based = {index + 1 for index in target_indices}

    changed = False
    removed_numberplates: list[int] = []
    removed_selector_ordinals: dict[int, list[int]] = {}
    removed_placement_count = 0
    decremented_placements = 0
    new_payload_fields: list[ProtoField] = []
    current_numberplate_index = 0
    for field in payload_fields:
        if field.number == 51 and field.wire == 2:
            if current_numberplate_index in target_indices_set:
                removed_numberplates.append(current_numberplate_index)
                changed = True
            else:
                new_payload_fields.append(field)
            current_numberplate_index += 1
            continue
        if field.number == 52 and field.wire == 2:
            placement_fields = parse_fields(field.value)
            surface_counts: dict[int, int] = {}
            new_placement_fields: list[ProtoField] = []
            placement_changed = False
            for placement_field in placement_fields:
                if placement_field.wire != 2:
                    new_placement_fields.append(placement_field)
                    continue
                surface = int(placement_field.number)
                surface_counts[surface] = surface_counts.get(surface, 0) + 1
                ordinal = surface_counts[surface]
                nested = parse_fields(placement_field.value)
                numberplate_ref = None
                for nested_field in nested:
                    if nested_field.number == 2 and nested_field.wire == 0:
                        numberplate_ref = int(nested_field.value)
                        break
                if numberplate_ref in target_indices_1based:
                    removed_selector_ordinals.setdefault(surface, []).append(ordinal)
                    removed_placement_count += 1
                    placement_changed = True
                    continue
                if numberplate_ref is not None:
                    shift = sum(1 for index in target_indices if index + 1 < numberplate_ref)
                else:
                    shift = 0
                if numberplate_ref is not None and shift:
                    nested = _actor_set_or_append_field(nested, 2, 0, numberplate_ref - shift)
                    new_placement_fields.append(ProtoField(placement_field.number, placement_field.wire, build_fields(nested)))
                    decremented_placements += 1
                    placement_changed = True
                else:
                    new_placement_fields.append(placement_field)
            if placement_changed:
                changed = True
                new_payload_fields.append(ProtoField(field.number, field.wire, build_fields(new_placement_fields)))
            else:
                new_payload_fields.append(field)
            continue
        new_payload_fields.append(field)

    if not changed or not removed_numberplates:
        return actor_data, {"changed": False, "skipped": "actor did not contain a removable custom numberplate"}
    new_actor_fields = list(actor_fields)
    new_actor_fields[payload_index] = ProtoField(103, 2, build_fields(new_payload_fields))
    first_index = target_indices[0]
    return build_fields(new_actor_fields), {
        "changed": True,
        "numberplateIndex": first_index,
        "numberplateIndex1Based": first_index + 1,
        "numberplateIndices": removed_numberplates,
        "numberplateIndices1Based": [index + 1 for index in removed_numberplates],
        "removedPlacements": removed_placement_count,
        "decrementedPlacements": decremented_placements,
        "removedSelectorOrdinals": {str(surface): ordinals for surface, ordinals in removed_selector_ordinals.items()},
    }


def _same_actor_file(entry: dict[str, Any], game_root: Path, actor_path: Path) -> bool:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    other_path = _disk_from_game_rel(game_root, str(files.get("actor") or ""))
    if other_path is None:
        return False
    try:
        return other_path.resolve() == actor_path.resolve()
    except Exception:
        return str(other_path).lower() == str(actor_path).lower()


def _entry_visual_path(entry: dict[str, Any], game_root: Path) -> Path | None:
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    return _disk_from_game_rel(game_root, str(files.get("visual_preset") or ""))


def _numberplate_variant_indices(variant: dict[str, Any]) -> list[int]:
    indices: list[int] = []
    raw_indices = variant.get("numberplate_indices")
    if isinstance(raw_indices, (list, tuple)):
        for value in raw_indices:
            try:
                index = int(value)
            except Exception:
                continue
            if index > 0 and index not in indices:
                indices.append(index)
    if not indices:
        try:
            index = int(variant.get("numberplate_index"))
        except Exception:
            index = -1
        if index > 0:
            indices.append(index)
    return sorted(indices)


def _shift_numberplate_index_after_prune(index: int, removed_indices: list[int]) -> int:
    return int(index) - sum(1 for removed in removed_indices if int(removed) < int(index))


def _shift_numberplate_variant_metadata(variant: dict[str, Any], removed_indices: list[int]) -> dict[str, Any]:
    shifted = dict(variant)

    def shift_key(key: str) -> None:
        try:
            current = int(shifted.get(key))
        except Exception:
            return
        if current <= 0:
            return
        shifted[key] = _shift_numberplate_index_after_prune(current, removed_indices)
        companion = f"{key}_1based"
        if companion in shifted:
            shifted[companion] = int(shifted[key]) + 1

    for key in ("numberplate_index", "hood_numberplate_index", "left_numberplate_index", "right_numberplate_index"):
        shift_key(key)

    raw_indices = shifted.get("numberplate_indices")
    if isinstance(raw_indices, (list, tuple)):
        next_indices: list[int] = []
        for value in raw_indices:
            try:
                current = int(value)
            except Exception:
                continue
            if current <= 0:
                continue
            next_indices.append(_shift_numberplate_index_after_prune(current, removed_indices))
        shifted["numberplate_indices"] = next_indices
        shifted["numberplate_indices_1based"] = [index + 1 for index in next_indices]
    raw_selected = shifted.get("selected_by_surface")
    if isinstance(raw_selected, dict):
        next_selected: dict[str, int] = {}
        for surface, value in raw_selected.items():
            try:
                current = int(value)
            except Exception:
                continue
            if current <= 0:
                continue
            next_selected[str(surface)] = _shift_numberplate_index_after_prune(current, removed_indices)
        shifted["selected_by_surface"] = next_selected
    return shifted


def cleanup_actor_numberplate_variant_for_deleted_skin(entry: dict[str, Any], remaining_skins: list[dict[str, Any]], writer: GameFileWriter | None = None) -> dict[str, Any]:
    variant = entry.get("numberplate_variant") if isinstance(entry.get("numberplate_variant"), dict) else {}
    if variant.get("owns_actor_numberplate") is not True:
        return {"changed": False, "skipped": "numberplate variant is not marked as owned by LiveryLab"}
    target_indices = _numberplate_variant_indices(variant)
    if not target_indices:
        return {"changed": False, "skipped": "deleted skin did not own a custom numberplate variant"}

    game_root = normalize_game_root(str(entry.get("game_root", "")))
    files = entry.get("files", {}) if isinstance(entry.get("files"), dict) else {}
    actor_path = _disk_from_game_rel(game_root, str(files.get("actor") or ""))
    if actor_path is None or not actor_path.exists():
        return {"changed": False, "skipped": "actor file not found", "numberplateIndices": target_indices}

    actor_data = actor_path.read_bytes()
    same_actor_entries = [row for row in remaining_skins if isinstance(row, dict) and _same_actor_file(row, game_root, actor_path)]
    for other in same_actor_entries:
        other_variant = other.get("numberplate_variant") if isinstance(other.get("numberplate_variant"), dict) else {}
        other_indices = _numberplate_variant_indices(other_variant)
        shared_indices = sorted(set(other_indices).intersection(target_indices))
        if shared_indices:
            return {
                "changed": False,
                "skipped": "custom numberplate variant is still referenced by another skin",
                "actorPath": str(actor_path),
                "numberplateIndices": target_indices,
                "sharedNumberplateIndices": shared_indices,
            }
        visual_path = _entry_visual_path(other, game_root)
        if visual_path and visual_path.exists():
            try:
                selector = extract_visual_numberplate_selector(visual_path.read_bytes())
                selected_indices = sorted(set(_actor_selected_numberplate_indices_by_surface(actor_data, selector).values()))
                shared_selected = sorted(set(selected_indices).intersection(target_indices))
                if shared_selected:
                    return {
                        "changed": False,
                        "skipped": "another visual preset still selects this numberplate variant",
                        "actorPath": str(actor_path),
                        "numberplateIndices": target_indices,
                        "sharedNumberplateIndices": shared_selected,
                    }
            except Exception:
                continue

    new_actor_data, prune_info = prune_actor_numberplate_variants_data(actor_data, target_indices)
    if not prune_info.get("changed"):
        return {"actorPath": str(actor_path), "numberplateIndices": target_indices, **prune_info}

    actor_backup = _write_game_bytes(actor_path, new_actor_data, writer, label="actor numberplate cleanup")
    removed_ordinals: dict[int, list[int]] = {}
    for surface, ordinals in (prune_info.get("removedSelectorOrdinals") or {}).items():
        try:
            removed_ordinals[int(surface)] = sorted(int(value) for value in ordinals)
        except Exception:
            continue

    selector_updates: list[dict[str, Any]] = []
    metadata_updates: list[dict[str, Any]] = []
    for other in same_actor_entries:
        other_key = str(other.get("key") or other.get("skin_id") or "").strip()
        other_variant = other.get("numberplate_variant") if isinstance(other.get("numberplate_variant"), dict) else None
        if other_variant is not None:
            shifted_variant = _shift_numberplate_variant_metadata(other_variant, target_indices)
            if shifted_variant != other_variant:
                other_variant.clear()
                other_variant.update(shifted_variant)
                metadata_updates.append(
                    {
                        "skin": other_key,
                        "numberplateVariant": _json_ready(other_variant),
                    }
                )
        visual_path = _entry_visual_path(other, game_root)
        if not visual_path or not visual_path.exists():
            continue
        selector = extract_visual_numberplate_selector(visual_path.read_bytes())
        updated_selector = dict(selector)
        selector_changed = False
        for surface, ordinals in removed_ordinals.items():
            current = int(updated_selector.get(surface, 0) or 0)
            if current <= 0:
                continue
            shift = sum(1 for ordinal in ordinals if ordinal < current)
            if shift:
                updated_selector[surface] = max(1, current - shift)
                selector_changed = True
        if not selector_changed:
            continue
        if other_variant is not None:
            other_variant["selector"] = {str(surface): int(value) for surface, value in updated_selector.items()}
        changed, backup = update_visual_numberplate_selector_file(visual_path, updated_selector, writer=writer)
        selector_updates.append(
            {
                "skin": other_key,
                "path": str(visual_path),
                "changed": changed,
                "backup": str(backup) if backup else "",
                "selector": updated_selector,
            }
        )

    return {
        **prune_info,
        "actorPath": str(actor_path),
        "actorBackup": str(actor_backup) if actor_backup else "",
        "selectorUpdates": selector_updates,
        "metadataUpdates": metadata_updates,
    }


def add_actor_resource_data(actor_data: bytes, new_rel: str, field_number: int) -> tuple[bytes, bool]:
    new_rel = norm_slashes(new_rel)
    actor_fields = parse_fields(actor_data)
    changed = False
    new_actor_fields: list[ProtoField] = []
    for f in actor_fields:
        if f.number != 103 or f.wire != 2:
            new_actor_fields.append(f); continue
        payload_fields = parse_fields(f.value)
        new_payload_fields: list[ProtoField] = []
        patched_resource_block = False
        for pf in payload_fields:
            if pf.number != 13 or pf.wire != 2:
                new_payload_fields.append(pf); continue
            resource_fields = parse_fields(pf.value)
            existing = [is_printable_utf8(rf.value) for rf in resource_fields if rf.number == field_number and rf.wire == 2]
            if new_rel in existing:
                new_payload_fields.append(pf); patched_resource_block = True; continue
            insert_at = None
            for idx, rf in enumerate(resource_fields):
                if rf.number == field_number and rf.wire == 2:
                    insert_at = idx + 1
            new_field = ProtoField(field_number, 2, new_rel.encode("utf-8"))
            if insert_at is None: resource_fields.append(new_field)
            else: resource_fields.insert(insert_at, new_field)
            new_payload_fields.append(ProtoField(pf.number, pf.wire, build_fields(resource_fields)))
            changed = True; patched_resource_block = True
        new_actor_fields.append(ProtoField(f.number, f.wire, build_fields(new_payload_fields)) if patched_resource_block else f)
    return (build_fields(new_actor_fields), changed) if changed else (actor_data, False)


def remove_actor_resource_data(actor_data: bytes, rel_to_remove: str, field_number: int) -> tuple[bytes, bool]:
    target = norm_slashes(rel_to_remove)
    actor_fields = parse_fields(actor_data)
    changed = False
    new_actor_fields: list[ProtoField] = []
    for field in actor_fields:
        if field.number != 103 or field.wire != 2:
            new_actor_fields.append(field)
            continue
        payload_fields = parse_fields(field.value)
        new_payload_fields: list[ProtoField] = []
        patched_resource_block = False
        for payload_field in payload_fields:
            if payload_field.number != 13 or payload_field.wire != 2:
                new_payload_fields.append(payload_field)
                continue
            resource_fields = parse_fields(payload_field.value)
            kept_resource_fields: list[ProtoField] = []
            removed_here = False
            for resource_field in resource_fields:
                if resource_field.number == field_number and resource_field.wire == 2:
                    text = is_printable_utf8(resource_field.value)
                    if text is not None and norm_slashes(text) == target:
                        changed = True
                        removed_here = True
                        continue
                kept_resource_fields.append(resource_field)
            if removed_here:
                new_payload_fields.append(ProtoField(payload_field.number, payload_field.wire, build_fields(kept_resource_fields)))
                patched_resource_block = True
            else:
                new_payload_fields.append(payload_field)
        new_actor_fields.append(ProtoField(field.number, field.wire, build_fields(new_payload_fields)) if patched_resource_block else field)
    return (build_fields(new_actor_fields), True) if changed else (actor_data, False)


def patch_actor_visualpreset_file(actor_path: Path, new_visual_rel: str, writer: GameFileWriter | None = None) -> tuple[bool, Path | None]:
    old_data = actor_path.read_bytes()
    new_data, changed = add_actor_resource_data(old_data, new_visual_rel, 11)
    if not changed:
        return False, None
    backup = _write_game_bytes(actor_path, new_data, writer, label="actor visual preset registration")
    return True, backup


def patch_actor_mechanicalpreset_file(actor_path: Path, new_mechanical_rel: str, writer: GameFileWriter | None = None) -> tuple[bool, Path | None]:
    old_data = actor_path.read_bytes()
    new_data, changed = add_actor_resource_data(old_data, new_mechanical_rel, 10)
    if not changed:
        return False, None
    backup = _write_game_bytes(actor_path, new_data, writer, label="actor mechanical preset registration")
    return True, backup


def remove_actor_visualpreset_file(actor_path: Path, visual_rel: str, writer: GameFileWriter | None = None) -> tuple[bool, Path | None]:
    old_data = actor_path.read_bytes()
    new_data, changed = remove_actor_resource_data(old_data, visual_rel, 11)
    if not changed:
        return False, None
    backup = _write_game_bytes(actor_path, new_data, writer, label="actor visual preset removal")
    return True, backup


def remove_actor_mechanicalpreset_file(actor_path: Path, mechanical_rel: str, writer: GameFileWriter | None = None) -> tuple[bool, Path | None]:
    old_data = actor_path.read_bytes()
    new_data, changed = remove_actor_resource_data(old_data, mechanical_rel, 10)
    if not changed:
        return False, None
    backup = _write_game_bytes(actor_path, new_data, writer, label="actor mechanical preset removal")
    return True, backup


def extract_mechanicalpreset_info(data: bytes) -> dict[str, Any]:
    info: dict[str, Any] = {
        "manufacturer": None,
        "name": None,
        "short_name": None,
        "compatible_rim": None,
        "visual_preset": None,
        "visual_presets": [],
        "aftermarket_parts": [],
        "all_strings": collect_proto_strings(data),
    }
    parts: list[str] = []
    visuals: list[str] = []
    for f in parse_fields(data):
        if f.wire != 2:
            continue
        text = is_printable_utf8(f.value)
        if text is None:
            continue
        if f.number == 1:
            info["manufacturer"] = text
        elif f.number == 2:
            info["name"] = text
        elif f.number == 3:
            info["short_name"] = text
        elif f.number == 5:
            info["compatible_rim"] = text
        elif f.number == 8:
            visuals.append(text)
        elif f.number == 9:
            parts.append(text)
    info["visual_presets"] = visuals
    info["visual_preset"] = visuals[0] if visuals else None
    info["aftermarket_parts"] = parts
    return info


def inspect_mechanicalpreset_readable(mechanical_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    path = Path(mechanical_path)
    result = _inspect_readable_proto_file(path, root=root)
    info = extract_mechanicalpreset_info(path.read_bytes())
    compatible_parts = [
        str(ref.get("path"))
        for ref in result.get("references", [])
        if ref.get("category") == "compatible_part"
    ]
    descriptions = [
        str(ref.get("path"))
        for ref in result.get("references", [])
        if ref.get("category") == "description"
    ]
    result.update(
        {
            "manufacturer": info.get("manufacturer"),
            "name": info.get("name"),
            "short_name": info.get("short_name"),
            "compatible_rim": info.get("compatible_rim"),
            "visual_preset": info.get("visual_preset"),
            "visual_presets": info.get("visual_presets", []),
            "aftermarket_parts": info.get("aftermarket_parts", []),
            "compatible_parts": compatible_parts,
            "descriptions": descriptions,
        }
    )
    return result


def mechanicalpreset_readable_summary(result: dict[str, Any]) -> str:
    lines = [
        f"Mechanical Preset: {Path(str(result.get('file'))).name}",
        f"Size: {int(result.get('size_bytes') or 0):,} bytes",
        f"Manufacturer: {result.get('manufacturer') or '-'}",
        f"Name: {result.get('name') or '-'}",
        f"Short name: {result.get('short_name') or '-'}",
        f"Compatible Rim: {result.get('compatible_rim') or '-'}",
        f"Visual Presets: {len(result.get('visual_presets', []))}",
        f"Aftermarket Parts: {len(result.get('aftermarket_parts', []))}",
        f"Compatible Parts: {len(result.get('compatible_parts', []))}",
        f"Descriptions: {len(result.get('descriptions', []))}",
        "",
        "Visual Presets:",
    ]
    for visual in result.get("visual_presets", []):
        lines.append(f"  - {visual}")
    if result.get("aftermarket_parts"):
        lines.extend(["", "Aftermarket Parts:"])
        for part in result.get("aftermarket_parts", []):
            lines.append(f"  - {part}")
    if result.get("compatible_parts"):
        lines.extend(["", "Compatible Parts:"])
        for part in result.get("compatible_parts", []):
            lines.append(f"  - {part}")
    if result.get("descriptions"):
        lines.extend(["", "Descriptions:"])
        for description in result.get("descriptions", []):
            lines.append(f"  - {description}")
    lines.append("")
    lines.extend(_readable_common_lines(result))
    return "\n".join(lines)


def add_visual_to_mechanicalpreset_data(mech_data: bytes, new_visual_rel: str) -> tuple[bytes, bool]:
    """Add a .visualcarpreset reference to an existing shared .mechanicalcarpreset."""
    new_visual_rel = norm_slashes(new_visual_rel)
    fields = parse_fields(mech_data)
    existing = [is_printable_utf8(f.value) for f in fields if f.number == 8 and f.wire == 2]
    if new_visual_rel in existing:
        return mech_data, False
    insert_at: int | None = None
    for idx, f in enumerate(fields):
        if f.number == 8 and f.wire == 2:
            insert_at = idx + 1
    if insert_at is None:
        for idx, f in enumerate(fields):
            if f.number == 5 and f.wire == 2:
                insert_at = idx + 1
    new_field = ProtoField(8, 2, new_visual_rel.encode("utf-8"))
    if insert_at is None:
        fields.append(new_field)
    else:
        fields.insert(insert_at, new_field)
    return build_fields(fields), True


def remove_visual_from_mechanicalpreset_data(mech_data: bytes, visual_rel: str) -> tuple[bytes, bool]:
    """Remove an exact .visualcarpreset reference from an existing .mechanicalcarpreset."""
    target = norm_slashes(visual_rel)
    fields = parse_fields(mech_data)
    changed = False
    kept_fields: list[ProtoField] = []
    for field in fields:
        if field.number == 8 and field.wire == 2:
            text = is_printable_utf8(field.value)
            if text is not None and norm_slashes(text) == target:
                changed = True
                continue
        kept_fields.append(field)
    return (build_fields(kept_fields), True) if changed else (mech_data, False)


def patch_mechanicalpreset_visuals_file(mechanical_path: Path, new_visual_rel: str, writer: GameFileWriter | None = None) -> tuple[bool, Path | None]:
    old_data = mechanical_path.read_bytes()
    new_data, changed = add_visual_to_mechanicalpreset_data(old_data, new_visual_rel)
    if not changed:
        return False, None
    backup = _write_game_bytes(mechanical_path, new_data, writer, label="mechanical visual preset registration")
    return True, backup


def remove_mechanicalpreset_visual_file(mechanical_path: Path, visual_rel: str, writer: GameFileWriter | None = None) -> tuple[bool, Path | None]:
    old_data = mechanical_path.read_bytes()
    new_data, changed = remove_visual_from_mechanicalpreset_data(old_data, visual_rel)
    if not changed:
        return False, None
    backup = _write_game_bytes(mechanical_path, new_data, writer, label="mechanical visual preset removal")
    return True, backup


def make_mechanicalcarpreset(template_data: bytes, new_name: str, new_visual_rel: str, replacements: dict[str, str] | None = None) -> bytes:
    replacements = replacements or {}
    new_fields: list[ProtoField] = []
    for f in parse_fields(template_data):
        if f.wire == 2 and f.number == 2:
            new_fields.append(ProtoField(f.number, f.wire, new_name.encode("utf-8")))
        elif f.wire == 2 and f.number == 8:
            new_fields.append(ProtoField(f.number, f.wire, norm_slashes(new_visual_rel).encode("utf-8")))
        elif f.wire == 2:
            new_fields.append(ProtoField(f.number, f.wire, patch_strings_recursive(f.value, replacements)))
        else:
            new_fields.append(f)
    return build_fields(new_fields)


def find_mechanicalpreset_for_visual(root: Path, actor_path: Path | None, visual_template: Path | None, visual_rel: str | None = None) -> Path | None:
    if visual_rel is None and visual_template is not None:
        visual_rel = content_rel_from_disk(root, visual_template)
    candidates: list[Path] = []
    actor_candidate_keys: set[str] = set()
    content_root = root / "content" / "cars"
    if actor_path and actor_path.exists():
        try:
            for rel in actor_list_mechanicalpresets(actor_path.read_bytes()):
                p = resolve_content_path(root, rel)
                if p.exists():
                    candidates.append(p)
                    actor_candidate_keys.add(norm_slashes(str(p.resolve())).lower())
        except Exception:
            pass
    if not content_root.exists():
        content_root = None
    try:
        if content_root is not None:
            candidates.extend(content_root.rglob("*.mechanicalcarpreset"))
    except Exception:
        pass
    seen = set()
    registered_candidates: dict[str, Path] = {}
    for p in candidates:
        key = str(p.resolve()) if p.exists() else str(p)
        if key in seen or not p.exists():
            continue
        seen.add(key)
        registered_candidates[norm_slashes(str(p.resolve())).lower()] = p
        try:
            info = extract_mechanicalpreset_info(p.read_bytes())
            if visual_rel:
                visuals = [norm_slashes(str(v)) for v in info.get("visual_presets", [])]
                if norm_slashes(visual_rel) in visuals:
                    return p
        except Exception:
            continue
    # Some AC EVO cars pair additional official visual presets with a shared
    # mechanical preset only through car-local final states. Accept that
    # authoritative link when the referenced mechanical preset is also actor
    # registered, instead of requiring a duplicate visual entry in the preset.
    if visual_rel and visual_template is not None and visual_template.exists():
        visual_target = norm_slashes(visual_rel).lower()
        try:
            for finalstate_path in visual_template.parent.glob("*.carfinalstate"):
                if not finalstate_path.is_file():
                    continue
                finalstate_info = extract_carfinalstate_info(finalstate_path.read_bytes())
                finalstate_visual = norm_slashes(str(finalstate_info.get("visual_preset") or "")).lower()
                if finalstate_visual != visual_target:
                    continue
                mechanical_ref = norm_slashes(str(finalstate_info.get("mechanical_preset") or ""))
                mechanical_path = resolve_content_path(root, mechanical_ref) if mechanical_ref else None
                if mechanical_path is None or not mechanical_path.exists():
                    continue
                mechanical_key = norm_slashes(str(mechanical_path.resolve())).lower()
                registered = registered_candidates.get(mechanical_key)
                if registered is not None and mechanical_key in actor_candidate_keys:
                    return registered
        except Exception:
            pass
    if visual_template is not None:
        if visual_rel:
            return None
        stem = visual_template.stem.lower()
        # Common naming relation: preset_foo_visual_1 -> preset_foo_mech_1
        wanted = stem.replace("_visual_", "_mech_") + ".mechanicalcarpreset"
        try:
            if content_root is not None:
                for p in content_root.rglob(wanted):
                    if p.is_file():
                        return p
            for p in root.rglob(wanted):
                if p.is_file():
                    return p
        except Exception:
            pass
    return None


def make_carfinalstate_filename(car_content_path: str, mechanical_rel: str, visual_rel: str) -> str:
    car_name = split_content_path(car_content_path)[-1]
    mech_name = Path(norm_slashes(mechanical_rel).replace("\\", "/")).stem
    visual_name = Path(norm_slashes(visual_rel).replace("\\", "/")).stem
    return f"{car_name}_{mech_name}_{visual_name}.carfinalstate"


def _car_content_path_from_visual_rel(visual_rel: str | None) -> str:
    parts = split_content_path(norm_slashes(str(visual_rel or "")))
    if len(parts) >= 3 and parts[0].lower() == "content" and parts[1].lower() == "cars":
        return norm_slashes(str(Path(*parts[:3])))
    return ""


def carfinalstate_target_rel(car_content_path: str, mechanical_rel: str, visual_rel: str) -> str:
    filename = make_carfinalstate_filename(car_content_path, mechanical_rel, visual_rel)
    car_rel = norm_slashes(str(car_content_path or "")).strip("\\/")
    if car_rel:
        return norm_slashes(f"{car_rel}\\presets\\{filename}")
    return norm_slashes(f"system\\savedcars\\{filename}")


def _carfinalstate_search_dirs(root: Path, car_content_path: str | None = None, visual_rel: str | None = None) -> list[Path]:
    dirs: list[Path] = []
    seen: set[str] = set()

    def add(path: Path) -> None:
        key = str(path.resolve()).lower() if path.exists() else str(path).lower()
        if key in seen:
            return
        seen.add(key)
        dirs.append(path)

    car_rel = norm_slashes(str(car_content_path or "")).strip("\\/")
    if not car_rel:
        car_rel = _car_content_path_from_visual_rel(visual_rel)
    if car_rel:
        add(root / Path(*split_content_path(f"{car_rel}\\presets")))
    add(root / "system" / "savedcars")
    return dirs


def find_carfinalstate_for_visual(root: Path, visual_rel: str, mechanical_rel: str | None = None) -> Path | None:
    visual_target = norm_slashes(visual_rel)
    mechanical_target = norm_slashes(mechanical_rel) if mechanical_rel else None
    try:
        for folder in _carfinalstate_search_dirs(root, visual_rel=visual_rel):
            if not folder.exists():
                continue
            for path in folder.rglob("*.carfinalstate"):
                if not path.is_file():
                    continue
                try:
                    info = extract_carfinalstate_info(path.read_bytes())
                except Exception:
                    continue
                if norm_slashes(str(info.get("visual_preset") or "")) != visual_target:
                    continue
                if mechanical_target and norm_slashes(str(info.get("mechanical_preset") or "")) != mechanical_target:
                    continue
                return path
    except Exception:
        pass
    return None


def find_carfinalstate_template_for_car(root: Path, car_content_path: str, mechanical_rel: str | None = None) -> Path | None:
    car_folder = split_content_path(car_content_path)[-1]
    car_folder_lower = car_folder.lower()
    mechanical_target = norm_slashes(mechanical_rel) if mechanical_rel else None
    try:
        for folder in _carfinalstate_search_dirs(root, car_content_path=car_content_path):
            if not folder.exists():
                continue
            for path in folder.rglob("*.carfinalstate"):
                if not path.is_file():
                    continue
                try:
                    info = extract_carfinalstate_info(path.read_bytes())
                except Exception:
                    continue
                car_data_text = norm_slashes(str(info.get("car_data") or "")).lower()
                actor_text = norm_slashes(str(info.get("actor_path") or "")).lower()
                if car_folder_lower not in car_data_text and car_folder_lower not in actor_text:
                    continue
                if mechanical_target and norm_slashes(str(info.get("mechanical_preset") or "")) != mechanical_target:
                    continue
                return path
    except Exception:
        pass
    return None


def carfinalstate_matches_car(finalstate_path: Path, car_content_path: str, mechanical_rel: str | None = None) -> bool:
    try:
        data = finalstate_path.read_bytes()
        info = extract_carfinalstate_info(data)
    except Exception:
        return False
    car_folder = split_content_path(car_content_path)[-1].lower()
    car_data_text = norm_slashes(str(info.get("car_data") or "")).lower()
    actor_text = norm_slashes(str(info.get("actor_path") or "")).lower()
    if car_folder not in car_data_text and car_folder not in actor_text:
        return False
    if mechanical_rel:
        mechanical_target = norm_slashes(mechanical_rel)
        if norm_slashes(str(info.get("mechanical_preset") or "")) != mechanical_target:
            return False
    car_prefix = norm_slashes(car_content_path).lower() + "\\"
    allowed_car_prefixes = {car_prefix}
    for linked_key in ("compatible_rim", "rim_design"):
        linked_text = norm_slashes(str(info.get(linked_key) or "")).lower()
        if not linked_text.startswith("content\\cars\\"):
            continue
        linked_parts = split_content_path(linked_text)
        if len(linked_parts) >= 3:
            allowed_car_prefixes.add(norm_slashes(str(Path(*linked_parts[:3]))).lower() + "\\")
    for _path, value in collect_proto_strings(data):
        for _start, ref_text in _extract_content_references(value):
            normalized_ref = norm_slashes(ref_text).lower()
            if not normalized_ref.endswith(".material"):
                continue
            if not normalized_ref.startswith("content\\cars\\"):
                continue
            if normalized_ref.startswith("content\\cars\\common_assets\\"):
                continue
            if not any(normalized_ref.startswith(prefix) for prefix in allowed_car_prefixes):
                return False
    return True


def content_rel_from_disk(root: Path, path: Path) -> str | None:
    for base in [root, root.parent]:
        try:
            rel = path.resolve().relative_to(base.resolve())
            parts = list(rel.parts)
            if parts and parts[0].lower() == "content":
                return norm_slashes(str(Path(*parts)))
            if base.name.lower() == "content":
                return norm_slashes(str(Path("content") / rel))
        except Exception:
            pass
    return None


def _sanitize_start_number(value: Any, max_digits: int = 3) -> str:
    text = re.sub(r"\D", "", str(value or ""))
    return text[:max_digits]


def _visualpreset_start_number(data: bytes) -> str:
    for field in parse_fields(data):
        if field.number == 13 and field.wire == 0:
            return str(int(field.value))
    return ""


def extract_visualpreset_info(data: bytes) -> dict[str, Any]:
    external_status = official_variant_compatibility.inspect_visual_preset_bytes(data)
    info: dict[str, Any] = {
        "manufacturer": None,
        "name": None,
        "presentation": None,
        "cardesign": None,
        "rim_design": None,
        "start_number": _visualpreset_start_number(data),
        "externally_customizable_known": bool(external_status.get("readable")),
        "externally_customizable": bool(external_status.get("externallyCustomizable")),
        "all_strings": collect_proto_strings(data),
    }
    for f in parse_fields(data):
        if f.wire != 2: continue
        text = is_printable_utf8(f.value)
        if text is None: continue
        if f.number == 1: info["manufacturer"] = text
        elif f.number == 2: info["name"] = text
        elif f.number == 3: info["presentation"] = text
        elif f.number == 5: info["cardesign"] = text
        elif f.number == 6: info["rim_design"] = text
    return info


def inspect_visualpreset_readable(visual_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    path = Path(visual_path)
    data = path.read_bytes()
    info = extract_visualpreset_info(data)
    tree, proto_strings = _actor_walk_records(data)
    raw_strings = _actor_scan_ascii_strings(data, min_len=3)
    seen = {(s["offset"], s["length"], s["text"]) for s in proto_strings}
    extra_raw_strings = [
        {**s, "category": _actor_category_for_text(s["text"]), "source": "raw_scan"}
        for s in raw_strings
        if (s["offset"], s["length"], s["text"]) not in seen
    ]
    all_strings = sorted(proto_strings + extra_raw_strings, key=lambda row: (int(row["offset"]), str(row["text"])))
    root_path = Path(root) if root else None
    references: list[dict[str, Any]] = []
    seen_refs: set[tuple[str, int]] = set()
    categories: dict[str, list[dict[str, Any]]] = {}
    for row in all_strings:
        category = str(row.get("category") or "label")
        categories.setdefault(category, []).append(row)
        text = str(row.get("text") or "")
        refs = _extract_content_references(text)
        if not refs:
            continue
        for rel_start, ref_text in refs:
            ref_offset = int(row.get("offset") or 0) + rel_start
            key = (ref_text.lower(), ref_offset)
            if key in seen_refs:
                continue
            seen_refs.add(key)
            ref_category = _actor_category_for_text(ref_text)
            ref: dict[str, Any] = {
                "category": ref_category,
                "path": ref_text,
                "offset": ref_offset,
                "offset_hex": f"0x{ref_offset:X}",
                "field_path": row.get("path"),
                "exists": None,
                "resolved_path": None,
            }
            if root_path is not None:
                resolved = resolve_content_path(root_path, ref_text)
                ref["resolved_path"] = str(resolved)
                ref["exists"] = resolved.exists()
            references.append(ref)
    references.sort(key=lambda row: (int(row.get("offset") or 0), str(row.get("path") or "")))
    return {
        "file": str(path),
        "size_bytes": len(data),
        "manufacturer": info.get("manufacturer"),
        "name": info.get("name"),
        "presentation": info.get("presentation"),
        "cardesign": info.get("cardesign"),
        "rim_design": info.get("rim_design"),
        "start_number": info.get("start_number"),
        "strings": all_strings,
        "categories": categories,
        "references": references,
        "field_tree": tree,
    }


def extract_carfinalstate_info(data: bytes) -> dict[str, Any]:
    info: dict[str, Any] = {
        "car_data": None,
        "actor_path": None,
        "mechanical_preset": None,
        "visual_preset": None,
        "compatible_rim": None,
        "rim_design": None,
        "all_strings": collect_proto_strings(data),
    }
    for field in parse_fields(data):
        if field.wire != 2 or not isinstance(field.value, (bytes, bytearray)):
            continue
        text = is_printable_utf8(field.value)
        if field.number == 1 and text is not None:
            info["car_data"] = text
            continue
        if field.number == 2 and text is not None:
            info["actor_path"] = text
            continue
        if text is not None:
            continue
        try:
            subfields = parse_fields(bytes(field.value))
        except Exception:
            continue
        if field.number == 10:
            for subfield in subfields:
                if subfield.wire != 2:
                    continue
                subtext = is_printable_utf8(subfield.value)
                if subfield.number == 1 and subtext is not None:
                    info["mechanical_preset"] = subtext
                elif subfield.number == 2 and subtext is not None:
                    info["visual_preset"] = subtext
        elif field.number == 20:
            for subfield in subfields:
                if subfield.wire != 2:
                    continue
                subtext = is_printable_utf8(subfield.value)
                if subfield.number == 1 and subtext is not None:
                    info["compatible_rim"] = subtext
                elif subfield.number == 2 and subtext is not None:
                    info["rim_design"] = subtext
    return info


def _infer_start_number_from_material_path(material_path: str | None) -> str | None:
    if not material_path:
        return None
    normalized = norm_slashes(material_path)
    folder_match = re.search(r"\\skins\\[^\\]*?(\d{1,4})(?:\\|$)", normalized, re.I)
    file_match = re.search(r"[_-](\d{1,4})\.(?:material|texture|dds|texturemips)$", normalized, re.I)
    folder_number = folder_match.group(1) if folder_match else None
    file_number = file_match.group(1) if file_match else None
    if folder_number and file_number and folder_number == file_number:
        return folder_number
    return file_number or folder_number


def _replace_start_number_in_material_path(material_path: str, old_number: str, new_number: str) -> str:
    normalized = norm_slashes(material_path)
    updated = re.sub(
        rf"(\\skins\\[^\\]*?){re.escape(old_number)}(?=\\)",
        rf"\g<1>{new_number}",
        normalized,
        count=1,
        flags=re.I,
    )
    updated = re.sub(
        rf"([_-]){re.escape(old_number)}(?=\.(?:material|texture|dds|texturemips)$)",
        rf"\g<1>{new_number}",
        updated,
        count=1,
        flags=re.I,
    )
    return updated


def _replace_start_number_in_text(text: str, old_number: str, new_number: str) -> str:
    return re.sub(rf"(?<!\d){re.escape(old_number)}(?!\d)", new_number, text)


def inspect_carfinalstate_readable(finalstate_path: str | Path, root: str | Path | None = None) -> dict[str, Any]:
    path = Path(finalstate_path)
    result = _inspect_readable_proto_file(path, root=root)
    info = extract_carfinalstate_info(path.read_bytes())
    refs = result.get("references", [])
    ext_skin_material = next(
        (
            str(ref.get("path"))
            for ref in refs
            if ref.get("category") == "material" and "ext_skin" in str(ref.get("path") or "").lower()
        ),
        None,
    )
    result.update(
        {
            "car_data": info.get("car_data"),
            "actor_path": info.get("actor_path"),
            "mechanical_preset": info.get("mechanical_preset"),
            "visual_preset": info.get("visual_preset"),
            "compatible_rim": info.get("compatible_rim"),
            "rim_design": info.get("rim_design"),
            "ext_skin_material": ext_skin_material,
            "start_number": _infer_start_number_from_material_path(ext_skin_material),
            "materials": [str(ref.get("path")) for ref in refs if ref.get("category") == "material"],
            "colors": [str(ref.get("path")) for ref in refs if ref.get("category") == "color"],
            "compatible_parts": [str(ref.get("path")) for ref in refs if ref.get("category") == "compatible_part"],
        }
    )
    return result


def carfinalstate_readable_summary(result: dict[str, Any]) -> str:
    lines = [
        f"Car Final State: {Path(str(result.get('file'))).name}",
        f"Size: {int(result.get('size_bytes') or 0):,} bytes",
        f"Car Data: {result.get('car_data') or '-'}",
        f"Actor: {result.get('actor_path') or '-'}",
        f"Mechanical Preset: {result.get('mechanical_preset') or '-'}",
        f"Visual Preset: {result.get('visual_preset') or '-'}",
        f"Compatible Rim: {result.get('compatible_rim') or '-'}",
        f"Rim Design: {result.get('rim_design') or '-'}",
        f"EXT_SKIN Material: {result.get('ext_skin_material') or '-'}",
        f"Start Number: {result.get('start_number') or '-'}",
        f"Materials: {len(result.get('materials', []))}",
        f"Colors: {len(result.get('colors', []))}",
        f"Compatible Parts: {len(result.get('compatible_parts', []))}",
        "",
    ]
    lines.extend(_readable_common_lines(result))
    return "\n".join(lines)


def visualpreset_readable_summary(result: dict[str, Any]) -> str:
    lines = [
        f"Visual Preset: {Path(str(result.get('file'))).name}",
        f"Size: {int(result.get('size_bytes') or 0):,} bytes",
        f"Manufacturer: {result.get('manufacturer') or '-'}",
        f"Name: {result.get('name') or '-'}",
        f"Presentation: {result.get('presentation') or '-'}",
        f"Cardesign: {result.get('cardesign') or '-'}",
        f"Rim Design: {result.get('rim_design') or '-'}",
        f"Start Number: {result.get('start_number') or '-'}",
        "",
        f"Readable strings: {len(result.get('strings', []))}",
        f"References: {len(result.get('references', []))}",
        "",
        "Readable data by category:",
    ]
    categories = result.get("categories", {})
    if isinstance(categories, dict):
        for category in sorted(categories):
            rows = categories.get(category) or []
            lines.append(f"  {category}: {len(rows)}")
    lines.extend(["", "References:"])
    for ref in result.get("references", []):
        exists = ref.get("exists")
        exists_text = ""
        if exists is True:
            exists_text = " [found]"
        elif exists is False:
            exists_text = " [missing]"
        field = f" field {ref.get('field_path')}" if ref.get("field_path") else ""
        lines.append(f"{ref.get('offset_hex')} [{ref.get('category')}]{field}: {ref.get('path')}{exists_text}")
    lines.extend(["", "Strings:"])
    for row in result.get("strings", []):
        text = str(row.get("text", "")).replace("\r", "\\r").replace("\n", "\\n")
        field = f" field {row.get('path')}" if row.get("path") else ""
        lines.append(f"{row.get('offset_hex')} [{row.get('category')}]{field}: {text}")
    return "\n".join(lines)


def edit_visualpreset_data(data: bytes, manufacturer: str | None = None, name: str | None = None, presentation: str | None = None, cardesign: str | None = None, rim_design: str | None = None, start_number: str | None = None, replacements: dict[str, str] | None = None) -> bytes:
    replacements = replacements or {}
    normalized_replacements = {_normalize_exact_text(str(old)): str(new) for old, new in replacements.items()}
    new_fields: list[ProtoField] = []
    changed = False
    start_number_text = _sanitize_start_number(start_number) if start_number is not None else None
    start_number_written = False
    for field in parse_fields(data):
        if field.wire == 0 and field.number == 13 and start_number_text is not None:
            replacement_number = int(start_number_text or "0")
            start_number_written = True
            if int(field.value) != replacement_number:
                new_fields.append(ProtoField(field.number, field.wire, replacement_number))
                changed = True
            else:
                new_fields.append(field)
            continue
        if field.wire != 2 or not isinstance(field.value, (bytes, bytearray)):
            new_fields.append(field)
            continue
        replacement_text = None
        if field.number == 1 and manufacturer is not None:
            replacement_text = manufacturer
        elif field.number == 2 and name is not None:
            replacement_text = name
        elif field.number == 3 and presentation is not None:
            replacement_text = presentation
        elif field.number == 5 and cardesign is not None:
            replacement_text = norm_slashes(cardesign)
        elif field.number == 6 and rim_design is not None:
            replacement_text = norm_slashes(rim_design)
        if replacement_text is not None:
            current = is_printable_utf8(field.value) or ""
            if current != replacement_text:
                new_fields.append(ProtoField(field.number, field.wire, replacement_text.encode("utf-8")))
                changed = True
            else:
                new_fields.append(field)
            continue
        text = is_printable_utf8(field.value)
        if text is not None:
            key = _normalize_exact_text(text)
            new_text = normalized_replacements.get(key, text)
            if new_text != text:
                new_fields.append(ProtoField(field.number, field.wire, new_text.encode("utf-8")))
                changed = True
            else:
                new_fields.append(field)
            continue
        nested = patch_strings_recursive_exact(bytes(field.value), normalized_replacements)
        if nested != bytes(field.value):
            new_fields.append(ProtoField(field.number, field.wire, nested))
            changed = True
        else:
            new_fields.append(field)
    if start_number_text is not None and not start_number_written:
        new_fields.append(ProtoField(13, 0, int(start_number_text or "0")))
        changed = True
    return build_fields(new_fields) if changed else data


def update_visualpreset_file(visual_path: str | Path, manufacturer: str | None = None, name: str | None = None, presentation: str | None = None, cardesign: str | None = None, rim_design: str | None = None, start_number: str | None = None, replacements: dict[str, str] | None = None, writer: GameFileWriter | None = None) -> tuple[bool, Path | None]:
    path = Path(visual_path)
    old_data = path.read_bytes()
    new_data = edit_visualpreset_data(old_data, manufacturer=manufacturer, name=name, presentation=presentation, cardesign=cardesign, rim_design=rim_design, start_number=start_number, replacements=replacements)
    if new_data == old_data:
        return False, None
    backup = _write_game_bytes(path, new_data, writer, label="visual preset update")
    return True, backup


def _edit_string_fields_by_number(raw: bytes, replacements: dict[int, str]) -> bytes:
    new_fields: list[ProtoField] = []
    changed = False
    for field in parse_fields(raw):
        replacement_text = replacements.get(field.number)
        if field.wire == 2 and replacement_text is not None:
            current = is_printable_utf8(field.value)
            if current is not None and current != replacement_text:
                new_fields.append(ProtoField(field.number, field.wire, replacement_text.encode("utf-8")))
                changed = True
                continue
        new_fields.append(field)
    return build_fields(new_fields) if changed else raw


def edit_carfinalstate_data(data: bytes, car_data: str | None = None, actor_path: str | None = None, mechanical_preset: str | None = None, visual_preset: str | None = None, compatible_rim: str | None = None, rim_design: str | None = None, start_number: str | None = None, replacements: dict[str, str] | None = None) -> bytes:
    normalized_replacements = {_normalize_exact_text(str(old)): str(new) for old, new in (replacements or {}).items()}
    if start_number is not None:
        current = inspect_carfinalstate_readable_bytes(data)
        old_number = str(current.get("start_number") or "").strip()
        material_path = str(current.get("ext_skin_material") or "").strip()
        new_number = str(start_number).strip()
        if old_number and new_number and old_number != new_number and material_path:
            existing_target = normalized_replacements.get(_normalize_exact_text(material_path), material_path)
            normalized_replacements[_normalize_exact_text(material_path)] = _replace_start_number_in_material_path(existing_target, old_number, new_number)
    new_fields: list[ProtoField] = []
    changed = False
    for field in parse_fields(data):
        if field.wire == 2 and field.number == 1 and car_data is not None:
            replacement = norm_slashes(car_data)
            current = is_printable_utf8(field.value) or ""
            if current != replacement:
                new_fields.append(ProtoField(field.number, field.wire, replacement.encode("utf-8")))
                changed = True
            else:
                new_fields.append(field)
            continue
        if field.wire == 2 and field.number == 2 and actor_path is not None:
            replacement = norm_slashes(actor_path)
            current = is_printable_utf8(field.value) or ""
            if current != replacement:
                new_fields.append(ProtoField(field.number, field.wire, replacement.encode("utf-8")))
                changed = True
            else:
                new_fields.append(field)
            continue
        if field.wire == 2 and field.number in {10, 20} and isinstance(field.value, (bytes, bytearray)):
            nested = bytes(field.value)
            if field.number == 10:
                nested = _edit_string_fields_by_number(
                    nested,
                    {
                        1: norm_slashes(mechanical_preset) if mechanical_preset is not None else None,
                        2: norm_slashes(visual_preset) if visual_preset is not None else None,
                    },
                )
            elif field.number == 20:
                nested = _edit_string_fields_by_number(
                    nested,
                    {
                        1: norm_slashes(compatible_rim) if compatible_rim is not None else None,
                        2: norm_slashes(rim_design) if rim_design is not None else None,
                    },
                )
            if normalized_replacements:
                nested = patch_strings_recursive(nested, normalized_replacements)
            if nested != bytes(field.value):
                new_fields.append(ProtoField(field.number, field.wire, nested))
                changed = True
            else:
                new_fields.append(field)
            continue
        if field.wire == 2 and normalized_replacements and isinstance(field.value, (bytes, bytearray)):
            text = is_printable_utf8(field.value)
            if text is not None:
                key = _normalize_exact_text(text)
                new_text = normalized_replacements.get(key, text)
                if new_text != text:
                    new_fields.append(ProtoField(field.number, field.wire, new_text.encode("utf-8")))
                    changed = True
                    continue
            else:
                nested = patch_strings_recursive(bytes(field.value), normalized_replacements)
                if nested != bytes(field.value):
                    new_fields.append(ProtoField(field.number, field.wire, nested))
                    changed = True
                    continue
        new_fields.append(field)
    if changed:
        return build_fields(new_fields)
    return data


def inspect_carfinalstate_readable_bytes(data: bytes) -> dict[str, Any]:
    refs: list[str] = []
    for _path, value in collect_proto_strings(data):
        refs.extend(ref_text for _start, ref_text in _extract_content_references(value))
    ext_skin_material = next((ref for ref in refs if ref.lower().endswith(".material") and "ext_skin" in ref.lower()), None)
    info = extract_carfinalstate_info(data)
    info.update(
        {
            "ext_skin_material": ext_skin_material,
            "start_number": _infer_start_number_from_material_path(ext_skin_material),
        }
    )
    return info


def update_carfinalstate_file(finalstate_path: str | Path, car_data: str | None = None, actor_path: str | None = None, mechanical_preset: str | None = None, visual_preset: str | None = None, compatible_rim: str | None = None, rim_design: str | None = None, start_number: str | None = None, replacements: dict[str, str] | None = None) -> tuple[bool, Path | None]:
    path = Path(finalstate_path)
    old_data = path.read_bytes()
    new_data = edit_carfinalstate_data(
        old_data,
        car_data=car_data,
        actor_path=actor_path,
        mechanical_preset=mechanical_preset,
        visual_preset=visual_preset,
        compatible_rim=compatible_rim,
        rim_design=rim_design,
        start_number=start_number,
        replacements=replacements,
    )
    if new_data == old_data:
        return False, None
    backup = safe_write_bytes(path, new_data)
    return True, backup


def make_visualcarpreset(template_data: bytes, new_name: str, new_cardesign_rel: str, replacements: dict[str, str] | None = None, presentation: str | None = None) -> bytes:
    replacements = replacements or {}
    presentation_text = presentation if presentation is not None else new_name
    new_fields: list[ProtoField] = []
    for f in parse_fields(template_data):
        if f.wire == 2 and f.number == 2:
            new_fields.append(ProtoField(f.number, f.wire, new_name.encode("utf-8")))
        elif f.wire == 2 and f.number == 3:
            new_fields.append(ProtoField(f.number, f.wire, presentation_text.encode("utf-8")))
        elif f.wire == 2 and f.number == 5:
            new_fields.append(ProtoField(f.number, f.wire, norm_slashes(new_cardesign_rel).encode("utf-8")))
        elif f.wire == 2:
            new_fields.append(ProtoField(f.number, f.wire, patch_strings_recursive(f.value, replacements)))
        else:
            new_fields.append(f)
    return build_fields(new_fields)


def find_visualpreset_for_cardesign(root: Path, actor_path: Path | None, template_cardesign: Path, template_cardesign_rel: str | None = None) -> Path | None:
    if template_cardesign_rel is None:
        template_cardesign_rel = content_rel_from_disk(root, template_cardesign)
    candidates: list[Path] = []
    content_root = root / "content" / "cars"
    if actor_path and actor_path.exists():
        try:
            for rel in actor_list_visualpresets(actor_path.read_bytes()):
                p = resolve_content_path(root, rel)
                if p.exists(): candidates.append(p)
        except Exception:
            pass
    if not content_root.exists():
        content_root = None
    try:
        if content_root is not None:
            candidates.extend(content_root.rglob("*.visualcarpreset"))
    except Exception:
        pass
    seen = set()
    for p in candidates:
        key = str(p.resolve()) if p.exists() else str(p)
        if key in seen or not p.exists(): continue
        seen.add(key)
        try:
            info = extract_visualpreset_info(p.read_bytes())
            if template_cardesign_rel and norm_slashes(str(info.get("cardesign") or "")) == norm_slashes(template_cardesign_rel):
                return p
        except Exception:
            continue
    m = re.match(r"design_(.+)_([0-9]+)$", template_cardesign.stem, re.I)
    if m:
        wanted = f"preset_{m.group(1)}_visual_{m.group(2)}.visualcarpreset".lower()
        try:
            if content_root is not None:
                for p in content_root.rglob(wanted):
                    if p.is_file(): return p
            for p in root.rglob(wanted):
                if p.is_file(): return p
        except Exception:
            pass
    return None


def _visualpreset_candidates_for_cardesign(root: Path, actor_path: Path | None, template_cardesign: Path, template_cardesign_rel: str | None = None) -> list[tuple[str, Path]]:
    if template_cardesign_rel is None:
        template_cardesign_rel = content_rel_from_disk(root, template_cardesign)
    target = norm_slashes(str(template_cardesign_rel or ""))
    if not target:
        return []
    candidates: list[Path] = []
    content_root = root / "content" / "cars"
    if actor_path and actor_path.exists():
        try:
            for rel in actor_list_visualpresets(actor_path.read_bytes()):
                path = resolve_content_path(root, rel)
                if path.exists():
                    candidates.append(path)
        except Exception:
            pass
    try:
        if content_root.exists():
            candidates.extend(content_root.rglob("*.visualcarpreset"))
    except Exception:
        pass
    rows: list[tuple[str, Path]] = []
    seen: set[str] = set()
    for path in candidates:
        if not path.exists():
            continue
        rel = content_rel_from_disk(root, path)
        key = norm_slashes(str(rel or path)).lower()
        if key in seen:
            continue
        seen.add(key)
        try:
            info = extract_visualpreset_info(path.read_bytes())
        except Exception:
            continue
        if norm_slashes(str(info.get("cardesign") or "")) == target:
            rows.append((norm_slashes(str(rel or path)), path))
    return rows


def _paint_relevant_compatiblepart_refs(game_root: Path, compatible_refs: Iterable[Any]) -> list[str]:
    relevant: list[str] = []
    seen: set[str] = set()
    for raw in compatible_refs:
        compatible_rel = normalize_game_rel_path(str(raw or ""))
        if not compatible_rel:
            continue
        carpart_rel = _carpart_ref_from_compatiblepart(game_root, compatible_rel)
        if not carpart_rel:
            continue
        slot = variant_parts.part_slot(compatible_rel, carpart_rel)
        if variant_parts.ignored_reason(slot, "", carpart_rel, False):
            continue
        for mesh_rel in _mesh_refs_from_carpart(game_root, carpart_rel):
            mesh_path = resolve_content_path(game_root, mesh_rel)
            if not mesh_path.exists():
                continue
            try:
                mesh_uv = inspect_mesh_uv_view(mesh_path, game_root)
            except Exception:
                continue
            skin_relevant = _mesh_has_skin_ranges(mesh_uv)
            preview_relevant = _variant_part_mesh_is_preview_candidate(mesh_rel, carpart_rel, skin_relevant)
            ignored = variant_parts.ignored_reason(slot, mesh_rel, carpart_rel, skin_relevant)
            if ignored or not (skin_relevant or preview_relevant):
                continue
            key = compatible_rel.lower()
            if key not in seen:
                seen.add(key)
                relevant.append(compatible_rel)
            break
    return relevant


def inspect_cardesign_paint_variant_options(root: str | Path, cardesign: str | Path, design: str | Path | None = None) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    cardesign_path = _resolve_template_path(game_root, cardesign)
    design_path = resolve_template_design_path(game_root, cardesign_path, Path(design) if design else None)
    cardesign_rel = content_rel_from_disk(game_root, cardesign_path) or _root_relative_path(game_root, cardesign_path)
    info = extract_design_info(cardesign_path.read_bytes(), design_path.read_bytes())
    car_content_path = info.car_content_path or ""
    actor_path = find_actor_file(game_root, car_content_path) if car_content_path else None
    if not actor_path or not actor_path.exists():
        return {"ambiguous": False, "options": [], "selectedIndex": -1, "reason": "actor missing"}

    rows: list[dict[str, Any]] = []
    visual_candidates = _visualpreset_candidates_for_cardesign(game_root, actor_path, cardesign_path, cardesign_rel)
    actor_mechanical_refs = _actor_mechanical_preset_refs(game_root, actor_path)
    standard_mechanical_refs = [ref for ref in actor_mechanical_refs if not _is_liverylab_managed_mechanical_ref(ref)]
    mechanical_refs = standard_mechanical_refs or actor_mechanical_refs
    for visual_rel, visual_path in visual_candidates:
        matching_mechanical_refs = [ref for ref in mechanical_refs if _mechanical_preset_has_visual(game_root, ref, visual_rel)]
        for mechanical_rel in matching_mechanical_refs:
            finalstate_path = find_carfinalstate_for_visual(game_root, visual_rel, mechanical_rel)
            if not finalstate_path or not finalstate_path.exists():
                continue
            try:
                final_refs = _proto_content_refs(finalstate_path.read_bytes(), (".compatiblepart",))
                visual_info = extract_visualpreset_info(visual_path.read_bytes())
            except Exception:
                continue
            paint_refs = _paint_relevant_compatiblepart_refs(game_root, final_refs)
            label = str(visual_info.get("presentation") or visual_info.get("name") or Path(visual_rel.replace("\\", "/")).stem).strip()
            if not label:
                label = Path(visual_rel.replace("\\", "/")).stem
            if paint_refs:
                short_mech = _mechanical_preset_display_name(game_root, mechanical_rel)
                if short_mech and short_mech not in label:
                    label = f"{label} / {short_mech}"
            rows.append({
                "label": label,
                "cardesign": norm_slashes(cardesign_rel),
                "visual": norm_slashes(visual_rel),
                "visualPath": str(visual_path),
                "mechanical": norm_slashes(mechanical_rel),
                "mechanicalPath": str(resolve_content_path(game_root, mechanical_rel)),
                "carfinalstate": _root_relative_path(game_root, finalstate_path),
                "carfinalstatePath": str(finalstate_path),
                "paintRelevantCompatibleParts": paint_refs,
                "paintRelevantCount": len(paint_refs),
            })

    signatures = {tuple(row.get("paintRelevantCompatibleParts") or []) for row in rows}
    ambiguous = len(rows) > 1 and len(signatures) > 1 and any(row.get("paintRelevantCompatibleParts") for row in rows)
    selected_index = -1
    if rows:
        selected_index = next((idx for idx, row in enumerate(rows) if row.get("paintRelevantCompatibleParts")), 0)
    return {
        "ambiguous": ambiguous,
        "options": _json_ready(rows),
        "selectedIndex": selected_index,
        "reason": "paint-relevant variants differ" if ambiguous else "",
    }


def _unique_existing_paths(paths: list[Path]) -> list[Path]:
    unique: list[Path] = []
    seen: set[str] = set()
    for path in paths:
        if not path or not path.exists():
            continue
        key = str(path.resolve()).lower()
        if key in seen:
            continue
        seen.add(key)
        unique.append(path)
    return unique


def _actor_resource_candidates(root: Path, actor_path: Path | None, suffix: str, actor_reader) -> list[Path]:
    candidates: list[Path] = []
    if actor_path and actor_path.exists():
        try:
            for rel in actor_reader(actor_path.read_bytes()):
                path = resolve_content_path(root, rel)
                if path.exists():
                    candidates.append(path)
        except Exception:
            pass
    content_root = root / "content" / "cars"
    try:
        if content_root.exists():
            candidates.extend(content_root.rglob(f"*{suffix}"))
    except Exception:
        pass
    return _unique_existing_paths(candidates)


def _visual_variant_display_name(info: dict[str, Any], path: Path) -> str:
    presentation = str(info.get("presentation") or "").strip()
    name = str(info.get("name") or "").strip()
    for value in (presentation, name):
        if value:
            return value
    return path.stem.replace("_", " ").strip().title()


def _mechanical_variant_display_name(info: dict[str, Any], path: Path) -> str:
    name = str(info.get("name") or "").strip()
    short_name = str(info.get("short_name") or "").strip()
    for value in (name, short_name):
        if value:
            return value
    return path.stem.replace("_", " ").strip().title()


def inspect_cardesign_visual_variant(
    root: str | Path,
    cardesign: str | Path,
    design: str | Path | None = None,
) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    cardesign_path = Path(cardesign)
    result: dict[str, Any] = {
        "status": "unknown",
        "message": "Visual variant could not be detected.",
        "cardesign": str(cardesign_path),
        "cardesignRel": "",
        "design": str(design or ""),
        "carContentPath": "",
        "carId": "",
        "visualVariants": [],
        "visualCount": 0,
        "mechanicalPresets": [],
        "mechanicalCount": 0,
    }
    if not cardesign_path.exists():
        result["status"] = "missing"
        result["message"] = "Cardesign file was not found."
        return result

    cardesign_rel = content_rel_from_disk(game_root, cardesign_path) or _root_relative_path(game_root, cardesign_path)
    result["cardesignRel"] = cardesign_rel
    design_path = Path(design) if design else None
    if design_path is None or not design_path.exists():
        try:
            design_rel, _rim_rel = extract_cardesign_info(cardesign_path.read_bytes())
            design_path = resolve_content_path(game_root, design_rel) if design_rel else cardesign_path.with_suffix(".design")
        except Exception:
            design_path = cardesign_path.with_suffix(".design")
    result["design"] = str(design_path) if design_path else ""

    car_content_path = ""
    if design_path and design_path.exists():
        try:
            info = extract_design_info(cardesign_path.read_bytes(), design_path.read_bytes())
            car_content_path = info.car_content_path or ""
        except Exception:
            car_content_path = ""
    if not car_content_path:
        parts = split_content_path(cardesign_rel)
        if len(parts) >= 3 and parts[0].lower() == "content" and parts[1].lower() == "cars":
            car_content_path = norm_slashes(str(Path(*parts[:3])))
    result["carContentPath"] = car_content_path
    result["carId"] = split_content_path(car_content_path)[-1] if car_content_path else ""

    actor_path = find_actor_file(game_root, car_content_path) if car_content_path else None
    visual_candidates = _actor_resource_candidates(game_root, actor_path, ".visualcarpreset", actor_list_visualpresets)
    visual_rows: list[dict[str, Any]] = []
    target_cardesign = norm_slashes(cardesign_rel).lower()
    for path in visual_candidates:
        try:
            info = extract_visualpreset_info(path.read_bytes())
        except Exception:
            continue
        visual_cardesign = norm_slashes(str(info.get("cardesign") or "")).lower()
        if visual_cardesign != target_cardesign:
            continue
        rel = content_rel_from_disk(game_root, path) or _root_relative_path(game_root, path)
        visual_rows.append({
            "path": str(path),
            "rel": rel,
            "name": str(info.get("name") or ""),
            "presentation": str(info.get("presentation") or ""),
            "label": _visual_variant_display_name(info, path),
            "manufacturer": str(info.get("manufacturer") or ""),
        })
    result["visualVariants"] = visual_rows
    result["visualCount"] = len(visual_rows)

    visual_rels = {norm_slashes(str(row.get("rel") or "")).lower() for row in visual_rows if row.get("rel")}
    mechanical_rows: list[dict[str, Any]] = []
    if visual_rels:
        mechanical_candidates = _actor_resource_candidates(game_root, actor_path, ".mechanicalcarpreset", actor_list_mechanicalpresets)
        for path in mechanical_candidates:
            try:
                info = extract_mechanicalpreset_info(path.read_bytes())
            except Exception:
                continue
            refs = [norm_slashes(str(ref)).lower() for ref in info.get("visual_presets", [])]
            matched = sorted(rel for rel in visual_rels if rel in refs)
            if not matched:
                continue
            rel = content_rel_from_disk(game_root, path) or _root_relative_path(game_root, path)
            mechanical_rows.append({
                "path": str(path),
                "rel": rel,
                "name": str(info.get("name") or ""),
                "shortName": str(info.get("short_name") or ""),
                "label": _mechanical_variant_display_name(info, path),
                "matchedVisuals": matched,
            })
    result["mechanicalPresets"] = mechanical_rows
    result["mechanicalCount"] = len(mechanical_rows)

    labels = []
    seen_labels: set[str] = set()
    for row in visual_rows:
        label = str(row.get("label") or "").strip()
        key = label.lower()
        if label and key not in seen_labels:
            seen_labels.add(key)
            labels.append(label)
    if labels:
        result["status"] = "detected"
        result["label"] = labels[0] if len(labels) == 1 else "Multiple visual variants"
        result["labels"] = labels
        result["message"] = labels[0] if len(labels) == 1 else ", ".join(labels)
    elif design_path and design_path.exists():
        try:
            display = str(extract_design_info(cardesign_path.read_bytes(), design_path.read_bytes()).display_name or "").strip()
        except Exception:
            display = ""
        if display:
            result["status"] = "fallback"
            result["label"] = display
            result["labels"] = [display]
            result["message"] = display
    return result

@dataclass
class CreateSkinResult:
    cardesign_path: Path
    design_path: Path
    skin_folder: Path | None
    material_path: Path | None
    copied_files: list[str]
    patched_files: list[str]
    info: DesignInfo
    actor_path: Path | None = None
    actor_patched: bool = False
    actor_backup: Path | None = None
    new_cardesign_rel: str | None = None
    visual_template_path: Path | None = None
    visual_preset_path: Path | None = None
    new_visual_rel: str | None = None
    actor_visual_patched: bool = False
    actor_visual_backup: Path | None = None
    mechanical_template_path: Path | None = None
    mechanical_preset_path: Path | None = None
    new_mechanical_rel: str | None = None
    actor_mechanical_patched: bool = False
    actor_mechanical_backup: Path | None = None
    mechanical_updated: bool = False
    mechanical_backup: Path | None = None
    carfinalstate_template_path: Path | None = None
    carfinalstate_path: Path | None = None
    new_carfinalstate_name: str | None = None
    carfinalstate_created: bool = False
    carfinalstate_backup: Path | None = None
    thumbnail_path: Path | None = None
    thumbnail_mips_path: Path | None = None
    thumbnail_created: bool = False
    visual_loc_key: str | None = None
    loc_updates: list[dict[str, Any]] | None = None
    my_skin_entry: dict[str, Any] | None = None
    numberplate_variant: dict[str, Any] | None = None
    source_skin: dict[str, Any] | None = None
    warnings: list[str] | None = None
    mechanical_preset_paths: list[Path] | None = None
    carfinalstate_paths: list[Path] | None = None
    thumbnail_paths: list[Path] | None = None
    thumbnail_mips_paths: list[Path] | None = None
    safety_refresh: dict[str, Any] | None = None


def _local_skin_root_candidate(game_root: Path, car_content_path: str, skin_id: str) -> Path:
    return local_skin_root_for(car_content_path, skin_id)


def _skin_id_conflict_details(
    game_root: Path,
    info: DesignInfo,
    skin_id: str,
    create_visual_preset: bool = True,
) -> dict[str, Any]:
    skin_id = sanitize_slug(skin_id)
    car_model = split_content_path(info.car_content_path or "")[-1] if info.car_content_path else ""
    local_root = _local_skin_root_candidate(game_root, info.car_content_path or "", skin_id)
    new_cardesign_rel = f"{info.car_content_path}\\skins\\{skin_id}.cardesign"
    new_design_rel = f"{info.car_content_path}\\skins\\{skin_id}.design"
    new_skin_rel = f"{info.car_content_path}\\skins\\{skin_id}"
    target_paths: list[dict[str, Any]] = [
        {"kind": "cardesign", "path": str(content_to_disk(local_root, new_cardesign_rel))},
        {"kind": "design", "path": str(content_to_disk(local_root, new_design_rel))},
        {"kind": "skin_folder", "path": str(content_to_disk(local_root, new_skin_rel))},
    ]
    if create_visual_preset:
        target_paths.append({"kind": "visual_preset", "path": str(content_to_disk(local_root, f"{info.car_content_path}\\presets\\{skin_id}.visualcarpreset"))})

    existing_paths: list[dict[str, Any]] = []
    for row in target_paths:
        path = Path(str(row.get("path") or ""))
        if path.exists():
            if path.is_dir():
                has_content = any(path.iterdir())
                if not has_content:
                    continue
            existing_paths.append({**row, "exists": True})

    registry_matches: list[dict[str, Any]] = []
    expected_key = f"{car_model}:{skin_id}".lower()
    for item in load_my_skins():
        item_key = str(item.get("key") or "").lower()
        item_car = str(item.get("car_model") or "").strip().lower()
        item_skin = str(item.get("skin_id") or "").strip().lower()
        if item_key == expected_key or (item_car == car_model.lower() and item_skin == skin_id.lower()):
            registry_matches.append(
                {
                    "key": item.get("key") or expected_key,
                    "display_name": item.get("display_name") or item.get("skin_id") or skin_id,
                    "local_root": item.get("local_root") or "",
                    "active": bool(item.get("active", True)),
                }
            )

    project_folder = get_livery_projects_root() / preview3d.safe_segment(car_model, "car") / preview3d.safe_segment(skin_id, "skin")
    project_files: list[Path] = []
    if project_folder.exists() and project_folder.is_dir():
        project_files = [path for path in project_folder.rglob("*") if path.is_file()]
    project_work_files = [
        path for path in project_files
        if path.suffix.lower() in LIVERY_PROJECT_WORK_EXTENSIONS
    ]
    project_png_files = [
        path for path in project_files
        if path.suffix.lower() == ".png"
    ]

    return {
        "skinId": skin_id,
        "carModel": car_model,
        "localRoot": str(local_root),
        "targetPaths": target_paths,
        "existingPaths": existing_paths,
        "registryMatches": registry_matches,
        "liveryProject": {
            "path": str(project_folder),
            "exists": project_folder.exists() and project_folder.is_dir(),
            "fileCount": len(project_files),
            "workFileCount": len(project_work_files),
            "pngFileCount": len(project_png_files),
        },
        "conflict": bool(existing_paths or registry_matches),
    }


def _suggest_available_skin_id(game_root: Path, info: DesignInfo, base_skin_id: str, create_visual_preset: bool = True) -> str:
    base = livery_identity.technical_skin_id(base_skin_id, "new_skin")
    for index in range(2, 1000):
        candidate = livery_identity.with_collision_suffix(base, str(index), livery_identity.TECHNICAL_ID_MAX_LENGTH)
        details = _skin_id_conflict_details(game_root, info, candidate, create_visual_preset=create_visual_preset)
        if not details.get("conflict"):
            return candidate
    return livery_identity.with_collision_suffix(base, datetime.now().strftime("%H%M%S"), livery_identity.TECHNICAL_ID_MAX_LENGTH)


def inspect_new_skin_id_conflict(
    root: Path,
    template_cardesign: Path,
    template_design: Path,
    new_skin_id: str,
    create_visual_preset: bool = True,
    actor_file: Path | None = None,
    visual_template: Path | None = None,
) -> dict[str, Any]:
    game_root = normalize_game_root(root)
    template_design = resolve_template_design_path(game_root, template_cardesign, template_design)
    info = extract_design_info(template_cardesign.read_bytes(), template_design.read_bytes())
    if not info.car_content_path:
        raise RuntimeError("Could not infer the car content path from the selected .cardesign/.design template.")
    skin_id = livery_identity.technical_skin_id(new_skin_id, "new_skin")
    details = _skin_id_conflict_details(game_root, info, skin_id, create_visual_preset=create_visual_preset)
    details["suggestedSkinId"] = _suggest_available_skin_id(game_root, info, skin_id, create_visual_preset=create_visual_preset) if details.get("conflict") else skin_id
    if create_visual_preset:
        try:
            actor_path = actor_file if actor_file and actor_file.exists() else find_actor_file(game_root, info.car_content_path)
            template_cardesign_rel = content_rel_from_disk(game_root, template_cardesign)
            visual_path = visual_template if visual_template and visual_template.exists() else find_visualpreset_for_cardesign(game_root, actor_path, template_cardesign, template_cardesign_rel)
            visual_rel = content_rel_from_disk(game_root, visual_path) if visual_path else ""
            actor_refs = _actor_mechanical_preset_refs(game_root, actor_path)
            standard_actor_refs = [ref for ref in actor_refs if not _is_liverylab_managed_mechanical_ref(ref)]
            availability_refs = standard_actor_refs or actor_refs
            matching_refs = [ref for ref in availability_refs if visual_rel and _mechanical_preset_has_visual(game_root, ref, visual_rel)]
            if availability_refs:
                details["variantAvailability"] = _mechanical_variant_notice(game_root, availability_refs, matching_refs or availability_refs)
                details["variantAvailability"]["sourceVisualPreset"] = norm_slashes(visual_rel)
        except Exception as exc:
            details["variantAvailability"] = {"message": f"Vehicle preset availability could not be checked: {exc}", "limited": False}
    return details


def _skin_id_conflict_message(details: dict[str, Any]) -> str:
    skin_id = str(details.get("skinId") or "").strip()
    car_model = str(details.get("carModel") or "").strip()
    suggested = str(details.get("suggestedSkinId") or "").strip()
    lines = [
        f"Skin ID already exists for {car_model}: {skin_id}",
        f"Use a different Skin ID, for example: {suggested}" if suggested else "Use a different Skin ID or enable Overwrite intentionally.",
    ]
    registry = details.get("registryMatches") if isinstance(details.get("registryMatches"), list) else []
    paths = details.get("existingPaths") if isinstance(details.get("existingPaths"), list) else []
    if registry:
        lines.append("Existing My Liveries entry: " + str(registry[0].get("key") or registry[0].get("display_name") or skin_id))
    if paths:
        lines.append("Existing target: " + str(paths[0].get("path") or ""))
    return "\n".join(lines)


def validate_create_skin_mechanical_matrix(root: Path, car_content_path: str, actor_path: Path | None, create_visual_preset: bool, source_visual_rel: str | None = None, source_mechanical_rel: str | None = None) -> dict[str, Any]:
    if not create_visual_preset:
        return {"ok": True, "mechanicalPresets": [], "carfinalstateTemplates": []}
    if actor_path is None or not actor_path.exists():
        return {"ok": False, "reason": "car actor missing", "mechanicalPresets": [], "carfinalstateTemplates": []}
    try:
        mechanical_refs = actor_list_mechanicalpresets(actor_path.read_bytes())
    except Exception as exc:
        return {"ok": False, "reason": f"could not read actor mechanical presets: {exc}", "mechanicalPresets": [], "carfinalstateTemplates": []}
    unique_refs: list[str] = []
    seen: set[str] = set()
    for ref in mechanical_refs:
        normalized = normalize_game_rel_path(ref)
        if not normalized:
            continue
        key = normalized.lower()
        if key in seen:
            continue
        seen.add(key)
        unique_refs.append(normalized)
    standard_refs = [ref for ref in unique_refs if not _is_liverylab_managed_mechanical_ref(ref)]
    if standard_refs:
        unique_refs = standard_refs
    source_visual_rel = normalize_game_rel_path(str(source_visual_rel or ""))
    if source_visual_rel:
        matching_refs = [ref for ref in unique_refs if _mechanical_preset_has_visual(root, ref, source_visual_rel)]
        if matching_refs:
            unique_refs = matching_refs
        elif source_mechanical_rel:
            source_mechanical_rel = normalize_game_rel_path(str(source_mechanical_rel or ""))
            if source_mechanical_rel and any(ref.lower() == source_mechanical_rel.lower() for ref in unique_refs):
                unique_refs = [ref for ref in unique_refs if ref.lower() == source_mechanical_rel.lower()]
    if not unique_refs:
        return {"ok": False, "reason": "actor has no mechanical presets", "mechanicalPresets": [], "carfinalstateTemplates": []}
    templates: list[dict[str, Any]] = []
    missing: list[str] = []
    for ref in unique_refs:
        template = find_carfinalstate_template_for_car(root, car_content_path, ref)
        if template and template.exists():
            templates.append({"mechanical": ref, "template": str(template)})
        else:
            missing.append(ref)
    if missing:
        return {
            "ok": False,
            "reason": "missing carfinalstate template for mechanical preset",
            "mechanicalPresets": unique_refs,
            "carfinalstateTemplates": templates,
            "missing": missing,
        }
    return {"ok": True, "mechanicalPresets": unique_refs, "carfinalstateTemplates": templates, "missing": []}


def create_skin(root: Path, template_cardesign: Path, template_design: Path, new_skin_id: str, new_display_name: str, overwrite: bool = False, copy_source_skin: bool = True, patch_actor: bool = True, actor_file: Path | None = None, create_visual_preset: bool = False, visual_template: Path | None = None, patch_actor_visual: bool = True, update_mechanical_preset: bool = False, mechanical_template: Path | None = None, patch_actor_mechanical: bool = True, create_carfinalstate: bool = False, carfinalstate_template: Path | None = None, preview_image: Path | None = None, create_numberplate_variant: bool = False, numberplate_font: str | None = None, numberplate_background: str | None = None, numberplate_mode: str | None = None, numberplate_hood_background: str | None = None, numberplate_left_background: str | None = None, numberplate_right_background: str | None = None, numberplate_template_id: str | None = None, numberplate_text_color: list[Any] | tuple[Any, ...] | None = None, numberplate_start_number: str | None = None, author: str = "", description: str = "", bypass_liverylab_safety: bool = False, allow_unregistered_project_template: bool = False) -> CreateSkinResult:
    if create_numberplate_variant and not NUMBERPLATE_SKIN_TEMPLATE_WORKFLOW_ENABLED:
        create_numberplate_variant = False
        numberplate_font = None
        numberplate_background = None
        numberplate_mode = None
        numberplate_hood_background = None
        numberplate_left_background = None
        numberplate_right_background = None
        numberplate_template_id = None
        numberplate_text_color = None
        numberplate_start_number = None
    root = normalize_game_root(root)
    template_support = inspect_skin_template_support(root, template_cardesign, template_design)
    if template_support.get("status") == "unsupported" and not allow_unregistered_project_template:
        reasons = template_support.get("reasons") if isinstance(template_support.get("reasons"), list) else []
        detail = "\n".join(f"- {row}" for row in reasons if str(row).strip())
        raise RuntimeError(
            f"{template_support.get('message') or SKIN_TEMPLATE_UNSUPPORTED_MESSAGE}"
            + (f"\n{detail}" if detail else "")
        )
    template_design = resolve_template_design_path(root, template_cardesign, template_design)
    cardesign_data = template_cardesign.read_bytes(); design_data = template_design.read_bytes()
    info = extract_design_info(cardesign_data, design_data)
    if not info.ext_skin_material_path:
        raise RuntimeError("Could not find EXT_SKIN material path in template .design")
    if not info.car_content_path:
        raise RuntimeError("Could not infer the car content path from the selected .cardesign/.design template.")
    paint_only_upgrade = False
    if not info.source_skin_folder_name:
        if _livery_material_upgrade_available(root, info):
            paint_only_upgrade = True
            if not copy_source_skin:
                raise RuntimeError("Paint-only livery upgrade needs local material/texture generation. Please keep Copy source folder enabled.")
        else:
            raise RuntimeError(
                "The selected .cardesign/.design is not suitable as a livery template because its EXT_SKIN material points outside a skin folder. "
                "Please choose a design whose EXT_SKIN material is stored below content\\cars\\<car_id>\\skins\\<design_id>."
            )
    elif _skin_local_livery_upgrade_available(root, info):
        paint_only_upgrade = True
        if not copy_source_skin:
            raise RuntimeError("Skin-local livery material upgrade needs local texture generation. Please keep Copy source folder enabled.")
    effective_display_name = livery_identity.normalize_display_name(new_display_name, "New Skin 2026")
    new_skin_id = livery_identity.technical_skin_id(new_skin_id or effective_display_name, "new_skin")
    if not overwrite:
        conflict = _skin_id_conflict_details(root, info, new_skin_id, create_visual_preset=create_visual_preset)
        if conflict.get("conflict"):
            new_skin_id = _suggest_available_skin_id(root, info, new_skin_id, create_visual_preset=create_visual_preset)
    material_filename = paint_only_livery.MATERIAL_FILENAME if paint_only_upgrade else (info.source_material_filename or Path(info.ext_skin_material_path.replace("\\", "/")).name)
    new_design_rel = f"{info.car_content_path}\\skins\\{new_skin_id}.design"
    new_cardesign_rel = f"{info.car_content_path}\\skins\\{new_skin_id}.cardesign"
    new_material_rel = f"{info.car_content_path}\\skins\\{new_skin_id}\\{material_filename}"
    write_root = get_local_skin_root(root, info.car_content_path, new_skin_id)
    new_cardesign_disk = content_to_disk(write_root, new_cardesign_rel)
    new_design_disk = content_to_disk(write_root, new_design_rel)
    new_skin_folder_disk = content_to_disk(write_root, f"{info.car_content_path}\\skins\\{new_skin_id}")
    new_material_disk = content_to_disk(write_root, new_material_rel)
    copied: list[str] = []; patched: list[str] = []
    warnings: list[str] = []
    source_skin_rel = f"{info.car_content_path}\\skins\\{info.source_skin_folder_name}" if info.source_skin_folder_name else ""
    new_skin_rel = f"{info.car_content_path}\\skins\\{new_skin_id}"
    design_slot_materials = extract_design_slot_materials(design_data)
    material_replacements = {norm_slashes(info.ext_skin_material_path): norm_slashes(new_material_rel)}
    for slot_id, slot_materials in design_slot_materials.items():
        if not _is_ext_skin_design_slot(slot_id):
            continue
        for slot_material in slot_materials:
            normalized_slot_material = norm_slashes(slot_material)
            if not normalized_slot_material or normalized_slot_material in material_replacements:
                continue
            target_material = _retarget_material_from_source_skin(slot_material, info.car_content_path, info.source_skin_folder_name, new_skin_id)
            if target_material:
                material_replacements[normalized_slot_material] = norm_slashes(target_material)
    source_actor_path = None
    actor_path = None
    actor_rel = None
    if patch_actor or (create_visual_preset and patch_actor_visual) or actor_file or create_numberplate_variant or create_carfinalstate:
        source_actor_path = actor_file if actor_file else find_actor_file(root, info.car_content_path)
        if source_actor_path is None:
            raise FileNotFoundError("Could not locate car .actor file for actor/visual-preset patching")
        actor_path = source_actor_path
        actor_rel = content_rel_from_disk(root, actor_path)
        if actor_rel and not norm_slashes(actor_rel).lower().startswith(norm_slashes(info.car_content_path).lower() + "\\"):
            raise RuntimeError("The selected .actor template does not match the selected car design.")
    template_cardesign_rel = content_rel_from_disk(root, template_cardesign)
    visual_template_path = None
    visual_preset_disk = None
    new_visual_rel = None
    visual_template_rel = None
    matrix_mechanical_refs: list[str] = []
    matrix_template_by_mechanical: dict[str, Path] = {}
    if create_visual_preset:
        visual_template_path = visual_template if visual_template and visual_template.exists() else find_visualpreset_for_cardesign(root, actor_path, template_cardesign, template_cardesign_rel)
        if visual_template_path is None:
            raise FileNotFoundError("Could not locate matching .visualcarpreset template. Please choose one manually.")
        visual_template_rel = content_rel_from_disk(root, visual_template_path)
        visual_info = extract_visualpreset_info(visual_template_path.read_bytes())
        visual_cardesign = norm_slashes(str(visual_info.get("cardesign") or ""))
        if template_cardesign_rel and visual_cardesign and visual_cardesign != norm_slashes(template_cardesign_rel):
            raise RuntimeError("The selected .visualcarpreset template does not match the selected .cardesign.")
        matrix_check = validate_create_skin_mechanical_matrix(root, info.car_content_path, actor_path, create_visual_preset, visual_template_rel)
        if not matrix_check.get("ok"):
            missing = matrix_check.get("missing") if isinstance(matrix_check.get("missing"), list) else []
            detail = "\n".join(f"- {row}" for row in missing[:8])
            suffix = f"\nMissing templates:\n{detail}" if detail else ""
            raise RuntimeError(
                "This car cannot create a game-visible custom skin because its mechanical/carfinalstate setup is incomplete. "
                f"Reason: {matrix_check.get('reason') or 'unknown'}."
                f"{suffix}"
            )
        actor_mechanical_refs = _actor_mechanical_preset_refs(root, actor_path)
        target_mechanical_refs = matrix_check.get("mechanicalPresets") if isinstance(matrix_check.get("mechanicalPresets"), list) else []
        matrix_mechanical_refs = [normalize_game_rel_path(str(row or "")) for row in target_mechanical_refs if normalize_game_rel_path(str(row or ""))]
        for row in matrix_check.get("carfinalstateTemplates") or []:
            if not isinstance(row, dict):
                continue
            mechanical_ref = normalize_game_rel_path(str(row.get("mechanical") or ""))
            template_path = Path(str(row.get("template") or ""))
            if mechanical_ref and template_path.exists():
                matrix_template_by_mechanical[mechanical_ref.lower()] = template_path
        variant_notice = _mechanical_variant_notice(root, actor_mechanical_refs, [str(row) for row in target_mechanical_refs])
        if variant_notice.get("message"):
            warnings.append(str(variant_notice.get("message")))
        visual_stem = new_skin_id[:-len(".visualcarpreset")] if new_skin_id.lower().endswith(".visualcarpreset") else new_skin_id
        new_visual_rel = f"{info.car_content_path}\\presets\\{visual_stem}.visualcarpreset"
        visual_preset_disk = content_to_disk(write_root, new_visual_rel)
        if visual_preset_disk.exists() and not overwrite:
            raise FileExistsError(f"Target visual preset exists: {visual_preset_disk}")
    mechanical_template_path = None
    mechanical_preset_disk = None
    new_mechanical_rel = None
    matrix_mechanical_paths: list[Path] = []
    for ref in matrix_mechanical_refs:
        mechanical_path = _disk_from_game_rel(root, ref)
        if mechanical_path and mechanical_path.exists():
            matrix_mechanical_paths.append(mechanical_path)
    if update_mechanical_preset:
        if not create_visual_preset or not new_visual_rel or visual_preset_disk is None:
            raise RuntimeError("Mechanical Preset update needs a new Visual Preset. Please enable 'Visual Preset erstellen'.")
        mechanical_template_path = matrix_mechanical_paths[0] if matrix_mechanical_paths else (mechanical_template if mechanical_template and mechanical_template.exists() else find_mechanicalpreset_for_visual(root, actor_path, visual_template_path, visual_template_rel))
        if mechanical_template_path is None:
            warnings.append("Mechanical Preset skipped: no compatible .mechanicalcarpreset includes the selected visual preset.")
            update_mechanical_preset = False
            create_carfinalstate = False
        else:
            mechanical_preset_disk = mechanical_template_path
            new_mechanical_rel = content_rel_from_disk(root, mechanical_template_path)
            mechanical_info = extract_mechanicalpreset_info(mechanical_template_path.read_bytes())
            if visual_template_rel and norm_slashes(visual_template_rel) not in {norm_slashes(str(item)) for item in mechanical_info.get("visual_presets", [])}:
                warnings.append("Mechanical Preset skipped: the selected .mechanicalcarpreset does not include the selected visual preset.")
                mechanical_template_path = None
                mechanical_preset_disk = None
                new_mechanical_rel = None
                update_mechanical_preset = False
                create_carfinalstate = False
    elif mechanical_template and mechanical_template.exists():
        mechanical_template_path = mechanical_template
        new_mechanical_rel = content_rel_from_disk(root, mechanical_template_path)
        if not matrix_mechanical_paths:
            matrix_mechanical_paths = [mechanical_template_path]
            if new_mechanical_rel:
                matrix_mechanical_refs = [new_mechanical_rel]
    carfinalstate_template_path = None
    carfinalstate_disk = None
    new_carfinalstate_name = None
    matrix_carfinalstate_jobs: list[tuple[str, Path, Path, str]] = []
    if create_carfinalstate:
        if not create_visual_preset or not new_visual_rel or visual_preset_disk is None:
            raise RuntimeError("Car Final State creation needs a new Visual Preset. Please enable 'Visual Preset erstellen'.")
        if not matrix_mechanical_refs and new_mechanical_rel:
            matrix_mechanical_refs = [new_mechanical_rel]
        if not matrix_mechanical_refs:
            raise RuntimeError("Car Final State creation needs a mechanical preset/template to be available.")
        for mechanical_ref in matrix_mechanical_refs:
            template_for_mechanical = None
            if carfinalstate_template and carfinalstate_template.exists() and carfinalstate_matches_car(carfinalstate_template, info.car_content_path, mechanical_ref):
                template_for_mechanical = carfinalstate_template
            if template_for_mechanical is None:
                template_for_mechanical = find_carfinalstate_for_visual(root, visual_template_rel or "", mechanical_ref)
                if template_for_mechanical and not carfinalstate_matches_car(template_for_mechanical, info.car_content_path, mechanical_ref):
                    template_for_mechanical = None
            if template_for_mechanical is None:
                template_for_mechanical = matrix_template_by_mechanical.get(mechanical_ref.lower())
            if template_for_mechanical is None:
                template_for_mechanical = find_carfinalstate_template_for_car(root, info.car_content_path, mechanical_ref)
            if template_for_mechanical is None:
                raise FileNotFoundError(f"Could not locate matching .carfinalstate template for mechanical preset: {mechanical_ref}")
            target_rel = carfinalstate_target_rel(info.car_content_path, mechanical_ref, new_visual_rel)
            target_name = Path(*split_content_path(target_rel)).name
            target_disk = write_root / Path(*split_content_path(target_rel))
            if target_disk.exists() and not overwrite:
                raise FileExistsError(f"Target car final state exists: {target_disk}")
            matrix_carfinalstate_jobs.append((mechanical_ref, template_for_mechanical, target_disk, target_name))
        if matrix_carfinalstate_jobs:
            new_mechanical_rel, carfinalstate_template_path, carfinalstate_disk, new_carfinalstate_name = matrix_carfinalstate_jobs[0]
    writer = GameFileWriter(
        write_root,
        "create local skin",
        [
            path
            for path in (
                new_cardesign_disk,
                new_design_disk,
                new_skin_folder_disk,
                visual_preset_disk,
                *(job[2] for job in matrix_carfinalstate_jobs),
            )
            if path
        ],
    )
    local_only = True
    if copy_source_skin:
        if paint_only_upgrade:
            copied, patched = create_paint_only_livery_skin_assets(
                root,
                info,
                new_skin_id,
                new_skin_folder_disk,
                new_material_disk,
                overwrite=overwrite,
                writer=writer,
                output_root=write_root,
            )
            extra_copied, extra_patched = copy_retargeted_skin_local_materials(
                root,
                write_root,
                info,
                material_replacements,
                new_material_rel,
                overwrite=overwrite,
                writer=writer,
            )
            copied.extend(extra_copied)
            patched.extend(extra_patched)
        else:
            src_material = find_material_file(root, info.ext_skin_material_path)
            if src_material is None:
                raise FileNotFoundError(f"Could not locate source material on disk: {info.ext_skin_material_path}")
            copied = copytree_or_copyfiles(src_material.parent, new_skin_folder_disk, overwrite=overwrite, writer=writer)
            copied_src_material = new_skin_folder_disk / src_material.name
            if copied_src_material.exists() and copied_src_material != new_material_disk:
                if new_material_disk.exists() and not overwrite:
                    raise FileExistsError(f"Target material file exists: {new_material_disk}")
                _move_game_file(copied_src_material, new_material_disk, writer, label="skin material")
                copied = [str(new_material_disk) if entry == str(copied_src_material) else entry for entry in copied]
            replacements = {
                norm_slashes(info.ext_skin_material_path): norm_slashes(new_material_rel),
                norm_slashes(source_skin_rel): norm_slashes(new_skin_rel),
            }
            patched = patch_copied_files_for_new_skin(new_skin_folder_disk, replacements, writer=writer)
            neutral_material_maps = neutralize_ready_template_material_textures(new_skin_folder_disk, writer=writer)
            patched.extend(
                str(item.get("texturemips"))
                for item in neutral_material_maps.get("items", [])
                if item.get("status") == "updated" and item.get("texturemips")
            )
            extra_copied, extra_patched = localize_external_ext_skin_material_textures(
                root,
                write_root,
                new_material_disk,
                new_skin_rel,
                new_skin_id,
                overwrite=overwrite,
                writer=writer,
            )
            copied.extend(extra_copied)
            patched.extend(extra_patched)
    new_cardesign = make_cardesign(cardesign_data, new_design_rel)
    new_design = make_design(design_data, effective_display_name, info.ext_skin_material_path, new_material_rel, material_replacements=material_replacements)
    if copy_source_skin:
        removed_unreferenced = prune_copied_skin_files_to_references(write_root, new_skin_folder_disk, new_skin_rel, [new_design], copied, writer=writer)
        if removed_unreferenced:
            removed_set = {str(Path(path).resolve()).lower() for path in removed_unreferenced}
            copied = [entry for entry in copied if str(Path(entry).resolve()).lower() not in removed_set]
    for target, data in [(new_cardesign_disk, new_cardesign), (new_design_disk, new_design)]:
        if target.exists() and not overwrite:
            raise FileExistsError(f"Target file exists: {target}")
        _write_game_bytes(target, data, writer, label="skin design file")
    actor_patched = False
    actor_backup = None
    if not local_only and patch_actor and actor_path is not None:
        actor_patched, actor_backup = patch_actor_file(actor_path, new_cardesign_rel, writer=writer)

    actor_visual_patched = False
    actor_visual_backup = None
    driver_skin_sync: dict[str, Any] = {}
    def driver_sync_has_assets(sync: dict[str, Any]) -> bool:
        plan = sync.get("plan") if isinstance(sync.get("plan"), dict) else {}
        return bool(plan.get("target_driverfinalstate_ref") and plan.get("driver_materials"))

    if create_visual_preset:
        old_visual = extract_visualpreset_info(visual_template_path.read_bytes())
        replacements: dict[str, str] = {}
        if old_visual.get("cardesign"):
            replacements[norm_slashes(str(old_visual.get("cardesign")))] = norm_slashes(new_cardesign_rel)
        replacements.update(material_replacements)
        visual_data = make_visualcarpreset(visual_template_path.read_bytes(), effective_display_name, new_cardesign_rel, replacements, presentation=effective_display_name)
        visual_data, ext_banner_visual_removed = ext_banner.remove_visualpreset_ext_banner_entry(visual_data)
        if ext_banner_visual_removed:
            patched.append(str(visual_preset_disk))
        _write_game_bytes(visual_preset_disk, visual_data, writer, label="visual preset")
        driver_setup_warnings: list[str] = []
        driver_asset_setup_supported = driver_library.is_driver_car_supported(
            getattr(info, "car_model", None),
            info.car_content_path,
        )
        if driver_asset_setup_supported:
            try:
                primary_driver_sync = driver_skin.localize_driver_skin_assets(
                    root,
                    write_root,
                    info.car_content_path,
                    new_skin_id,
                    visual_template_path,
                    visual_preset_disk,
                    design_path=template_design,
                    target_design_path=new_design_disk,
                    overwrite=overwrite,
                    writer=writer,
                )
                if driver_sync_has_assets(primary_driver_sync):
                    driver_skin_sync = primary_driver_sync
                else:
                    driver_setup_warnings.extend(str(warning) for warning in (primary_driver_sync.get("warnings") or []))
            except Exception as exc:
                driver_setup_warnings.append(str(exc))
            if not driver_sync_has_assets(driver_skin_sync) and default_racing_driver.is_default_racing_driver_car(info.car_content_path):
                try:
                    fallback_driver_sync = default_racing_driver.localize_default_racing_driver_assets(
                        root,
                        write_root,
                        info.car_content_path,
                        new_skin_id,
                        visual_preset_disk,
                        overwrite=overwrite,
                        writer=writer,
                    )
                    if driver_sync_has_assets(fallback_driver_sync):
                        driver_skin_sync = fallback_driver_sync
                        driver_setup_warnings = []
                    else:
                        driver_setup_warnings.extend(str(warning) for warning in (fallback_driver_sync.get("warnings") or []))
                        if not driver_skin_sync:
                            driver_skin_sync = fallback_driver_sync
                except Exception as exc:
                    driver_setup_warnings.append(f"Default racing driver setup skipped: {exc}")
        if driver_skin_sync:
            driver_copied = [str(path) for path in driver_skin_sync.get("copied") or []]
            driver_patched = [str(path) for path in driver_skin_sync.get("patched") or []]
            copied.extend(driver_copied)
            patched.extend(driver_patched)
            driver_source = ""
            plan = driver_skin_sync.get("plan") if isinstance(driver_skin_sync.get("plan"), dict) else {}
            if isinstance(plan, dict):
                driver_source = str(plan.get("source") or "")
            warning_prefix = "Default racing driver setup" if driver_source == "default_racing_driver" else "Driver asset setup"
            for warning in driver_skin_sync.get("warnings") or []:
                warnings.append(f"{warning_prefix}: {warning}")
        for warning in driver_setup_warnings:
            warnings.append(f"Driver asset setup: {warning}")
        driver_design_replacements = _driver_skin_sync_design_material_replacements(driver_skin_sync)
        if driver_design_replacements:
            material_replacements.update(driver_design_replacements)
            if visual_preset_disk and visual_preset_disk.exists():
                old_visual_data = visual_preset_disk.read_bytes()
                new_visual_data = edit_visualpreset_data(old_visual_data, replacements=driver_design_replacements)
                if new_visual_data != old_visual_data:
                    _write_game_bytes(visual_preset_disk, new_visual_data, writer, label="visual driver design references")
                    patched.append(str(visual_preset_disk))
        if not local_only and patch_actor_visual and actor_path is not None:
            actor_visual_patched, actor_visual_backup = patch_actor_visualpreset_file(actor_path, new_visual_rel, writer=writer)

    numberplate_variant: dict[str, Any] | None = None
    if not local_only and create_numberplate_variant:
        if not create_visual_preset or visual_preset_disk is None:
            raise RuntimeError("Numberplate variant creation needs a new Visual Preset. Please enable 'Visual erstellen'.")
        if actor_path is None:
            raise RuntimeError("Numberplate variant creation needs an actor file.")
        normalized_numberplate_mode = str(numberplate_mode or "").strip().lower()
        coerced_numberplate_text_color = _coerce_actor_numberplate_text_color(numberplate_text_color)
        if normalized_numberplate_mode in {"surface", "surfaces", "per_surface", "per-surface", "hood", "hood_split", "hood-left-right", "hood_left_right"}:
            numberplate_variant = apply_visual_numberplate_surface_variant(
                actor_path,
                visual_preset_disk,
                font_path=numberplate_font,
                hood_background_path=numberplate_hood_background,
                left_background_path=numberplate_left_background,
                right_background_path=numberplate_right_background,
                text_color=coerced_numberplate_text_color,
                writer=writer,
            )
        else:
            numberplate_variant = apply_visual_numberplate_variant(
                actor_path,
                visual_preset_disk,
                font_path=numberplate_font,
                background_path=numberplate_background,
                text_color=coerced_numberplate_text_color,
                writer=writer,
            )
        if numberplate_template_id and isinstance(numberplate_variant.get("variant"), dict):
            numberplate_variant["variant"]["template_id"] = str(numberplate_template_id)

    actor_mechanical_patched = False
    actor_mechanical_backup = None
    mechanical_updated = False
    mechanical_backup = None
    if not local_only and update_mechanical_preset:
        mechanical_updated, mechanical_backup = patch_mechanicalpreset_visuals_file(mechanical_template_path, new_visual_rel, writer=writer)

    carfinalstate_created = False
    carfinalstate_backup = None
    carfinalstate_paths: list[Path] = []
    thumbnail_texture_path = None
    thumbnail_mips_path = None
    thumbnail_created = False
    thumbnail_paths: list[Path] = []
    thumbnail_mips_paths: list[Path] = []
    visual_loc_key = None
    loc_updates: list[dict[str, Any]] = []
    for mechanical_ref, template_path, target_disk, _target_name in matrix_carfinalstate_jobs:
        old_final_bytes = template_path.read_bytes()
        old_final = extract_carfinalstate_info(old_final_bytes)
        replacements = {
            norm_slashes(str(old_final.get("visual_preset") or "")): norm_slashes(new_visual_rel),
            norm_slashes(str(old_final.get("mechanical_preset") or "")): norm_slashes(mechanical_ref),
            norm_slashes(str(old_final.get("compatible_rim") or "")): norm_slashes(str(old_final.get("compatible_rim") or "")),
            **material_replacements,
        }
        replacements = {k: v for k, v in replacements.items() if k and v}
        finalstate_data = edit_carfinalstate_data(
            old_final_bytes,
            car_data=str(old_final.get("car_data") or ""),
            actor_path=norm_slashes(content_rel_from_disk(root, actor_path) if actor_path else str(old_final.get("actor_path") or "")),
            mechanical_preset=mechanical_ref,
            visual_preset=new_visual_rel,
            compatible_rim=str(old_final.get("compatible_rim") or ""),
            rim_design=str(old_final.get("rim_design") or ""),
            replacements=replacements,
        )
        if visual_preset_disk and visual_preset_disk.exists():
            finalstate_data, _numberplate_selector_changed = advanced_numberplate_state.sync_carfinalstate_from_visual(
                finalstate_data,
                visual_preset_disk.read_bytes(),
            )
        backup = _write_game_bytes(target_disk, finalstate_data, writer, label="car final state")
        if carfinalstate_backup is None:
            carfinalstate_backup = backup
        carfinalstate_created = True
        carfinalstate_paths.append(target_disk)

    if not local_only and create_visual_preset and new_visual_rel and mechanical_template_path and new_mechanical_rel and visual_template_path:
        source_visual_rel = content_rel_from_disk(root, visual_template_path)
        source_mechanical_rel = content_rel_from_disk(root, mechanical_template_path)
        if source_visual_rel and source_mechanical_rel:
            if preview_image and preview_image.exists():
                thumbnail_texture_path, thumbnail_mips_path, thumbnail_created = build_generated_thumbnail_from_preview(
                    root,
                    info.car_content_path,
                    source_mechanical_rel,
                    source_visual_rel,
                    new_mechanical_rel,
                    new_visual_rel,
                    preview_image,
                    overwrite=overwrite,
                    writer=writer,
                )
                if thumbnail_texture_path:
                    thumbnail_paths.append(thumbnail_texture_path)
                if thumbnail_mips_path:
                    thumbnail_mips_paths.append(thumbnail_mips_path)
            else:
                thumbnail_texture_path, thumbnail_mips_path, thumbnail_created = clone_generated_thumbnail(
                    root,
                    info.car_content_path,
                    source_mechanical_rel,
                    source_visual_rel,
                    new_mechanical_rel,
                    new_visual_rel,
                    overwrite=overwrite,
                    writer=writer,
                )
                if thumbnail_texture_path:
                    thumbnail_paths.append(thumbnail_texture_path)
                if thumbnail_mips_path:
                    thumbnail_mips_paths.append(thumbnail_mips_path)

    if preview_image and preview_image.exists() and new_skin_folder_disk:
        store_skin_thumbnail_png(preview_image, write_root, game_rel_from_disk(write_root, new_skin_folder_disk), writer=writer)
    elif new_skin_folder_disk:
        ensure_skin_thumbnail_png_alias(write_root, game_rel_from_disk(write_root, new_skin_folder_disk), writer=writer)

    if create_visual_preset and effective_display_name:
        visual_loc_key = make_visual_loc_key(info.car_content_path, effective_display_name)
        loc_updates = []
    def source_rel(path: Path | None) -> str:
        if path is None:
            return ""
        return norm_slashes(game_rel_from_disk(root, path) or content_rel_from_disk(root, path) or str(path))

    if mechanical_template_path is None and matrix_mechanical_paths:
        mechanical_template_path = matrix_mechanical_paths[0]
    if mechanical_preset_disk is None and matrix_mechanical_paths:
        mechanical_preset_disk = matrix_mechanical_paths[0]
    if not new_mechanical_rel and matrix_mechanical_refs:
        new_mechanical_rel = matrix_mechanical_refs[0]

    source_skin_metadata: dict[str, Any] = {
        "cardesign": norm_slashes(template_cardesign_rel or source_rel(template_cardesign)),
        "design": source_rel(template_design),
        "skin_folder": norm_slashes(source_skin_rel),
        "material": norm_slashes(info.ext_skin_material_path or ""),
        "actor": norm_slashes(actor_rel or source_rel(actor_path)),
        "visual_preset": source_rel(visual_template_path),
        "mechanical_preset": source_rel(mechanical_template_path),
        "carfinalstate": source_rel(carfinalstate_template_path),
        "car_content_path": norm_slashes(info.car_content_path or ""),
        "skin_folder_name": str(info.source_skin_folder_name or ""),
        "display_name": str(info.display_name or ""),
    }
    if paint_only_upgrade:
        source_skin_metadata["template_mode"] = "skin_local_basecolor_upgrade" if info.source_skin_folder_name else "paint_only_livery_upgrade"
        source_skin_metadata["livery_material_template"] = paint_only_livery.TEMPLATE_MATERIAL_REL
    if visual_template_path and visual_template_path.exists():
        try:
            source_skin_metadata["numberplate_selector"] = _json_ready(extract_visual_numberplate_selector(visual_template_path.read_bytes()))
        except Exception:
            pass
    if driver_skin_sync.get("plan"):
        plan = driver_skin_sync.get("plan") if isinstance(driver_skin_sync.get("plan"), dict) else {}
        driverfinalstate_ref = plan.get("source_driverfinalstate_ref") or ""
        local_driverfinalstate_ref = plan.get("target_driverfinalstate_ref") or ""
        driver_materials = plan.get("driver_materials") or []
        if driverfinalstate_ref and local_driverfinalstate_ref and driver_materials:
            source_skin_metadata["driver_skin"] = {
                "source": str(plan.get("source") or "visual_preset_driver"),
                "driverfinalstate": driverfinalstate_ref,
                "local_driverfinalstate": local_driverfinalstate_ref,
                "materials": len(driver_materials),
                "design_slots": [
                    str(row.get("slot_id") or "")
                    for row in (plan.get("design_slots") or [])
                    if isinstance(row, dict) and str(row.get("slot_id") or "")
                ],
            }
    source_skin_metadata = {key: value for key, value in source_skin_metadata.items() if value not in (None, "")}

    result = CreateSkinResult(new_cardesign_disk, new_design_disk, new_skin_folder_disk if copy_source_skin else None, new_material_disk if copy_source_skin else None, copied, patched, info, actor_path, actor_patched, actor_backup, new_cardesign_rel, visual_template_path, visual_preset_disk, new_visual_rel, actor_visual_patched, actor_visual_backup, mechanical_template_path, mechanical_preset_disk, new_mechanical_rel, actor_mechanical_patched, actor_mechanical_backup, mechanical_updated, mechanical_backup, carfinalstate_template_path, carfinalstate_disk, new_carfinalstate_name, carfinalstate_created, carfinalstate_backup, thumbnail_texture_path, thumbnail_mips_path, thumbnail_created, visual_loc_key, loc_updates, None, numberplate_variant, source_skin_metadata, warnings)
    result.mechanical_preset_paths = matrix_mechanical_paths
    result.carfinalstate_paths = carfinalstate_paths
    result.thumbnail_paths = thumbnail_paths
    result.thumbnail_mips_paths = thumbnail_mips_paths
    entry, _saved = register_my_skin(root, result, new_skin_id, effective_display_name, start_number=None, local_root=write_root, active=False, author=author, description=description)
    try:
        local_ext_skin_sync = _ensure_entry_ext_skin_materials_are_local(entry, root)
        if local_ext_skin_sync.get("copied") or local_ext_skin_sync.get("patched"):
            result.warnings = result.warnings or []
            result.warnings.append("Additional EXT_SKIN materials were localized for this skin.")
    except Exception as exc:
        result.warnings = result.warnings or []
        result.warnings.append(f"Additional EXT_SKIN material setup skipped: {exc}")
    try:
        sf25_neutral_sync = _ensure_sf25_neutral_sponsor_materials(entry, root)
        if sf25_neutral_sync.get("changed"):
            result.warnings = result.warnings or []
            result.warnings.append("Ferrari SF25 sponsor and driver overlays were neutralized for this skin.")
    except Exception as exc:
        result.warnings = result.warnings or []
        result.warnings.append(f"Ferrari SF25 sponsor material setup skipped: {exc}")
    try:
        f2004_neutral_sync = _ensure_f2004_neutral_overlay_materials(entry, root)
        if f2004_neutral_sync.get("changed"):
            result.warnings = result.warnings or []
            result.warnings.append("Ferrari F2004 sponsor and decal overlays were neutralized for this skin.")
    except Exception as exc:
        result.warnings = result.warnings or []
        result.warnings.append(f"Ferrari F2004 overlay material setup skipped: {exc}")
    try:
        weissach_sticker_sync = _ensure_gt3rs_weissach_stickers_removed(entry)
        if weissach_sticker_sync.get("changed"):
            result.warnings = result.warnings or []
            result.warnings.append("GT3 RS Weissach sticker overlays were removed for this skin.")
    except Exception as exc:
        result.warnings = result.warnings or []
        result.warnings.append(f"GT3 RS Weissach sticker cleanup skipped: {exc}")
    try:
        weissach_carbon_sync = _ensure_gt3rs_weissach_carbon_ext_skin_neutralized(entry)
        if weissach_carbon_sync.get("changed"):
            result.warnings = result.warnings or []
            result.warnings.append("GT3 RS Weissach carbon EXT_SKIN materials were neutralized for this skin.")
    except Exception as exc:
        result.warnings = result.warnings or []
        result.warnings.append(f"GT3 RS Weissach carbon material cleanup skipped: {exc}")
    try:
        cayman_weissach_carbon_sync = _ensure_cayman_gt4rs_weissach_carbon_ext_skin_neutralized(entry)
        if cayman_weissach_carbon_sync.get("changed"):
            result.warnings = result.warnings or []
            result.warnings.append("718 Cayman GT4 RS Weissach carbon EXT_SKIN materials were neutralized for this skin.")
    except Exception as exc:
        result.warnings = result.warnings or []
        result.warnings.append(f"718 Cayman GT4 RS Weissach carbon material cleanup skipped: {exc}")
    try:
        paint_override_sync = _disable_livery_evo_paint_override_for_entry(entry)
        if paint_override_sync.get("changed"):
            result.warnings = result.warnings or []
            result.warnings.append("EVO paint override disabled for local EXT_SKIN material controls.")
    except Exception as exc:
        result.warnings = result.warnings or []
        result.warnings.append(f"EVO paint override setup skipped: {exc}")
    try:
        material_map_sync = _ensure_livery_material_maps_in_local_skin(
            entry,
            root,
            overwrite_existing=True,
            neutral_values=READY_TEMPLATE_NEUTRAL_MATERIAL_MAP_VALUES,
        )
        if material_map_sync.get("missing"):
            result.warnings = result.warnings or []
            result.warnings.append(f"Material map setup skipped for {material_map_sync.get('missing')} map(s).")
    except Exception as exc:
        result.warnings = result.warnings or []
        result.warnings.append(f"Material map setup skipped: {exc}")
    try:
        _ensure_road_plate_metadata_for_entry(entry, root)
    except Exception as exc:
        result.warnings = result.warnings or []
        result.warnings.append(f"Road car plate support check skipped: {exc}")
    sync_livery_project_thumbnail_to_local_skin(
        entry,
        preview_image=preview_image if preview_image and preview_image.exists() else None,
        promote_existing_local=False,
    )
    try:
        activation_dependencies = inspect_my_skin_activation_dependencies(entry, root)
        entry["activation_dependencies"] = _json_ready(activation_dependencies)
        if int(activation_dependencies.get("criticalCount") or 0) > 0:
            result.warnings = result.warnings or []
            result.warnings.append("Activation blocked: missing local content reference(s) found. See My Liveries details.")
    except Exception as exc:
        result.warnings = result.warnings or []
        result.warnings.append(f"Activation dependency check skipped: {exc}")
    _save_my_skin_entry(entry)
    if local_only and create_numberplate_variant:
        if not create_visual_preset or visual_preset_disk is None:
            raise RuntimeError("Numberplate variant creation needs a new Visual Preset. Please enable 'Visual erstellen'.")
        update_result = update_my_skin_numberplate(
            str(entry.get("key") or ""),
            font_path=numberplate_font,
            background_path=numberplate_background,
            mode=numberplate_mode,
            hood_background_path=numberplate_hood_background,
            left_background_path=numberplate_left_background,
            right_background_path=numberplate_right_background,
            template_id=numberplate_template_id,
            text_color=numberplate_text_color,
            start_number=numberplate_start_number or "000",
        )
        entry = update_result.get("item", entry) if isinstance(update_result, dict) else entry
        result.numberplate_variant = update_result.get("result") if isinstance(update_result, dict) else None
    safety_refresh = refresh_multiplayer_safety_after_my_skin_change(root, "create_skin", bypass_liverylab_safety)
    if safety_refresh.get("status") == "warning":
        result.warnings = result.warnings or []
        result.warnings.append(f"LiveryLab Safety manifest refresh skipped: {safety_refresh.get('error') or 'unknown error'}")
    result.safety_refresh = _json_ready(safety_refresh)
    result.my_skin_entry = entry
    return result


def analyze_skin_template(cardesign: Path, design: Path, root: Path | None = None) -> str:
    info = extract_design_info(cardesign.read_bytes(), design.read_bytes())
    lines = [f"Template cardesign: {cardesign}", f"Template design:     {design}", "", f"Display name:        {info.display_name}", f"Design path:         {info.design_path_from_cardesign}", f"Rim path:            {info.rim_path}", f"EXT_SKIN material:   {info.ext_skin_material_path}", f"Car content path:    {info.car_content_path}", f"Source skin folder:  {info.source_skin_folder_name}", f"Material filename:   {info.source_material_filename}"]
    if info.ext_skin_material_path and info.car_content_path and not info.source_skin_folder_name:
        if root and _paint_only_livery_upgrade_available(root, info):
            lines.extend(
                [
                    "",
                    "Paint-only livery upgrade:",
                    "This design uses a car-level EXT_SKIN material. LiveryLab can create a skin-local livery material with neutral auxiliary maps.",
                ]
            )
        else:
            lines.extend(
                [
                    "",
                    "Warning:",
                    "This design is not suitable as a livery template because its EXT_SKIN material is not stored in a skin folder.",
                    "Choose a design whose EXT_SKIN material path is below content\\cars\\<car_id>\\skins\\<design_id>.",
                ]
            )
    if root:
        actor = find_actor_file(root, info.car_content_path)
        visual = None
        mech = None
        finalstate = None
        if actor and actor.exists():
            cards = actor_list_cardesigns(actor.read_bytes())
            visuals = actor_list_visualpresets(actor.read_bytes())
            mechs = actor_list_mechanicalpresets(actor.read_bytes())
            lines.extend(["", f"Actor: {actor}", f"Cardesigns im Actor: {len(cards)}", f"Visual Presets im Actor: {len(visuals)}", f"Mechanical Presets im Actor: {len(mechs)}"])
            lines.extend([f"  - {c}" for c in cards])
            template_cardesign_rel = content_rel_from_disk(root, cardesign)
            visual = find_visualpreset_for_cardesign(root, actor, cardesign, template_cardesign_rel)
            visual_rel = content_rel_from_disk(root, visual) if visual else None
            mech = find_mechanicalpreset_for_visual(root, actor, visual, visual_rel) if visual else None
            mech_rel = content_rel_from_disk(root, mech) if mech else None
            finalstate = find_carfinalstate_for_visual(root, visual_rel, mech_rel) if visual_rel else None
        if visual:
            lines.extend(["", f"Visual template: {visual}"])
        if mech:
            lines.append(f"Mechanical template: {mech}")
        if finalstate:
            lines.append(f"Car Final State template: {finalstate}")
    return "\n".join(lines)

# -----------------------------------------------------------------------------
# GUI
# -----------------------------------------------------------------------------

class ToolApp(tk.Tk):
    def __init__(self) -> None:
        super().__init__()
        self.title(f"{APP_NAME} {APP_VERSION}")
        self.geometry("1260x820")
        self.queue: queue.Queue[tuple[str, str]] = queue.Queue()
        self.settings = load_app_settings()

        self.root_var = tk.StringVar(value=get_default_root_folder())
        self.texture_var = tk.StringVar()
        self.mips_var = tk.StringVar()
        self.dds_out_var = tk.StringVar()
        self.dds_format_var = tk.StringVar(value="auto")
        self.back_texture_var = tk.StringVar()
        self.dds_in_var = tk.StringVar()
        self.mips_out_var = tk.StringVar()
        self.material_var = tk.StringVar()
        self.material_report_dir_var = tk.StringVar()
        self.material_export_dir_var = tk.StringVar()
        self.skin_cardesign_var = tk.StringVar()
        self.skin_design_var = tk.StringVar()
        self.skin_actor_var = tk.StringVar()
        self.skin_visual_var = tk.StringVar()
        self.skin_mechanical_var = tk.StringVar()
        self.actor_viewer_var = tk.StringVar()
        self.design_view_cardesign_var = tk.StringVar()
        self.design_view_design_var = tk.StringVar()
        self.design_edit_cardesign_design_var = tk.StringVar()
        self.design_edit_cardesign_rim_var = tk.StringVar()
        self.design_edit_display_name_var = tk.StringVar()
        self.design_edit_manufacturer_var = tk.StringVar()
        self.design_edit_ext_skin_material_var = tk.StringVar()
        self.design_detail_map: dict[str, str] = {}
        self.skin_id_var = tk.StringVar(value="my_custom_skin")
        self.skin_name_var = tk.StringVar(value="My Custom Skin")
        self.skin_overwrite_var = tk.BooleanVar(value=False)
        self.skin_copy_source_var = tk.BooleanVar(value=True)
        self.skin_patch_actor_var = tk.BooleanVar(value=True)
        self.skin_create_visual_var = tk.BooleanVar(value=True)
        self.skin_patch_visual_actor_var = tk.BooleanVar(value=True)
        self.skin_create_mechanical_var = tk.BooleanVar(value=True)
        self.skin_patch_mechanical_actor_var = tk.BooleanVar(value=True)
        self.browser_show_all_var = tk.BooleanVar(value=False)
        self.browser_paths: dict[str, str] = {}
        self.browser_loaded: set[str] = set()
        self.browser_counter = 0
        self.material_result: dict[str, Any] | None = None

        self._build_ui()
        self.after(100, self._poll_queue)
        if self.root_var.get().strip():
            self.after(250, self.refresh_browser_tree)

    def _build_ui(self) -> None:
        top = ttk.Frame(self, padding=(10, 8))
        top.pack(fill=tk.X)
        ttk.Label(top, text=f"{APP_NAME} {APP_VERSION}", font=("Segoe UI", 12, "bold")).pack(side=tk.LEFT)
        ttk.Label(top, text="  Backups vor Ãœberschreiben: _acevo_backups", foreground="#666").pack(side=tk.LEFT)

        self.notebook = ttk.Notebook(self)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 8))
        self.tab_browser = ttk.Frame(self.notebook, padding=10)
        self.tab_convert = ttk.Frame(self.notebook, padding=10)
        self.tab_material = ttk.Frame(self.notebook, padding=10)
        self.tab_actor = ttk.Frame(self.notebook, padding=10)
        self.tab_design = ttk.Frame(self.notebook, padding=10)
        self.tab_skin = ttk.Frame(self.notebook, padding=10)
        self.tab_help = ttk.Frame(self.notebook, padding=10)
        self.notebook.add(self.tab_browser, text="Datei-Browser")
        self.notebook.add(self.tab_convert, text="TextureMips / DDS")
        self.notebook.add(self.tab_material, text="Material Inspector")
        self.notebook.add(self.tab_actor, text="Actor Viewer")
        self.notebook.add(self.tab_design, text="Design Viewer")
        self.notebook.add(self.tab_skin, text="Skin Creator")
        self.notebook.add(self.tab_help, text="Hinweise")
        self._build_browser_tab()
        self._build_convert_tab()
        self._build_material_tab()
        self._build_actor_tab()
        self._build_design_tab()
        self._build_skin_tab()
        self._build_help_tab()

        log_frame = ttk.LabelFrame(self, text="Log", padding=4)
        log_frame.pack(fill=tk.BOTH, expand=False, padx=10, pady=(0, 10))
        self.log = tk.Text(log_frame, height=7, wrap="word")
        self.log.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        sb = ttk.Scrollbar(log_frame, orient=tk.VERTICAL, command=self.log.yview)
        sb.pack(side=tk.RIGHT, fill=tk.Y)
        self.log.configure(yscrollcommand=sb.set)
        self.write("Bereit. Skin Creator ist jetzt in LiveryLab integriert.")

    def _row(self, parent, label, var, command=None, button="Browse", width=18):
        row = ttk.Frame(parent)
        row.pack(fill=tk.X, pady=3)
        ttk.Label(row, text=label, width=width).pack(side=tk.LEFT)
        ttk.Entry(row, textvariable=var).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 6))
        if command:
            ttk.Button(row, text=button, command=command).pack(side=tk.LEFT)
        return row

    def write(self, text: str) -> None:
        self.log.insert(tk.END, text + "\n")
        self.log.see(tk.END)

    def _run_threaded(self, fn):
        def worker():
            try:
                msg = fn()
                self.queue.put(("ok", str(msg)))
            except Exception as exc:
                self.queue.put(("err", f"{exc}\n\n{traceback.format_exc()}"))
        threading.Thread(target=worker, daemon=True).start()

    def _poll_queue(self):
        try:
            while True:
                status, msg = self.queue.get_nowait()
                self.write(("OK: " if status == "ok" else "ERROR: ") + msg)
                if status == "err":
                    messagebox.showerror("Fehler", msg.split("\n\n")[0])
        except queue.Empty:
            pass
        self.after(100, self._poll_queue)

    # Browser ----------------------------------------------------------------
    def _build_browser_tab(self):
        self._row(self.tab_browser, "Root Ordner", self.root_var, self.choose_root, "Ordner wÃ¤hlenâ€¦")
        opts = ttk.Frame(self.tab_browser); opts.pack(fill=tk.X, pady=4)
        ttk.Button(opts, text="Refresh", command=self.refresh_browser_tree).pack(side=tk.LEFT)
        ttk.Button(opts, text="Als Standard speichern", command=self.save_root_default).pack(side=tk.LEFT, padx=6)
        ttk.Checkbutton(opts, text="Alle Dateien anzeigen", variable=self.browser_show_all_var, command=self.refresh_browser_tree).pack(side=tk.LEFT, padx=10)
        paned = ttk.PanedWindow(self.tab_browser, orient=tk.HORIZONTAL); paned.pack(fill=tk.BOTH, expand=True, pady=(8,0))
        left = ttk.LabelFrame(paned, text="Ordnerstruktur", padding=5); right = ttk.LabelFrame(paned, text="Details / Aktionen", padding=5)
        paned.add(left, weight=3); paned.add(right, weight=2)
        cols = ("typ", "size")
        self.browser_tree = ttk.Treeview(left, columns=cols, show="tree headings")
        self.browser_tree.heading("#0", text="Name"); self.browser_tree.heading("typ", text="Typ"); self.browser_tree.heading("size", text="GrÃ¶ÃŸe")
        self.browser_tree.column("#0", width=380); self.browser_tree.column("typ", width=110); self.browser_tree.column("size", width=90, anchor="e")
        self.browser_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        sb = ttk.Scrollbar(left, orient=tk.VERTICAL, command=self.browser_tree.yview); sb.pack(side=tk.RIGHT, fill=tk.Y)
        self.browser_tree.configure(yscrollcommand=sb.set)
        self.browser_tree.bind("<<TreeviewOpen>>", self.on_browser_open)
        self.browser_tree.bind("<<TreeviewSelect>>", self.on_browser_select)
        self.browser_tree.bind("<Double-1>", self.on_browser_double)
        self.detail = tk.Text(right, height=20, wrap="word"); self.detail.pack(fill=tk.BOTH, expand=True)
        actions = ttk.Frame(right); actions.pack(fill=tk.X, pady=(8,0))
        ttk.Button(actions, text="Als Texture exportieren", command=self.browser_use_texture).pack(fill=tk.X, pady=2)
        ttk.Button(actions, text="Als Material analysieren", command=self.browser_use_material).pack(fill=tk.X, pady=2)
        ttk.Button(actions, text="Cardesign/Design anzeigen", command=self.browser_view_design_file).pack(fill=tk.X, pady=2)
        ttk.Button(actions, text="Als Skin-Vorlage Ã¼bernehmen", command=self.browser_use_skin_template).pack(fill=tk.X, pady=2)
        ttk.Button(actions, text=".actor anzeigen", command=self.browser_use_actor).pack(fill=tk.X, pady=2)
        ttk.Button(actions, text="Im Explorer Ã¶ffnen", command=self.browser_open_explorer).pack(fill=tk.X, pady=2)

    def choose_root(self):
        p = filedialog.askdirectory(title="Root Ordner wÃ¤hlen")
        if p:
            self.root_var.set(p); self.refresh_browser_tree()

    def save_root_default(self):
        root = self.root_var.get().strip()
        if not root or not Path(root).is_dir():
            messagebox.showwarning("Root ungÃ¼ltig", "Bitte gÃ¼ltigen Root-Ordner wÃ¤hlen."); return
        self.settings["default_root_folder"] = root
        path = save_app_settings(self.settings)
        self.write(f"Standard-Root gespeichert: {root}\nSettings: {path}")

    def _visible_file(self, p: Path) -> bool:
        if self.browser_show_all_var.get(): return True
        return p.suffix.lower() in {".texture", ".texturemips", ".material", ".dds", ".cardesign", ".design", ".actor", ".visualcarpreset", ".mechanicalcarpreset", ".png", ".jpg", ".jpeg", ".tga", ".json", ".ini", ".txt"}

    def _kind(self, p: Path) -> str:
        if p.is_dir(): return "Ordner"
        return {".texture":"Texture", ".texturemips":"Mips", ".material":"Material", ".dds":"DDS", ".cardesign":"Cardesign", ".design":"Design", ".actor":"Actor", ".visualcarpreset":"Visual Preset", ".mechanicalcarpreset":"Mechanical Preset"}.get(p.suffix.lower(), p.suffix[1:].upper() if p.suffix else "Datei")

    def _size(self, p: Path) -> str:
        if p.is_dir(): return ""
        try: size = p.stat().st_size
        except Exception: return ""
        for unit in ["B", "KB", "MB", "GB"]:
            if size < 1024 or unit == "GB":
                return f"{size:.1f} {unit}" if unit != "B" else f"{int(size)} B"
            size /= 1024
        return ""

    def refresh_browser_tree(self):
        for item in self.browser_tree.get_children(): self.browser_tree.delete(item)
        self.browser_paths.clear(); self.browser_loaded.clear(); self.browser_counter = 0
        root = Path(self.root_var.get().strip())
        if not root.is_dir(): return
        iid = self._insert_node("", root, root_node=True); self.browser_tree.item(iid, open=True); self._load_children(iid)

    def _new_iid(self, p: Path) -> str:
        self.browser_counter += 1
        iid = f"b{self.browser_counter}"; self.browser_paths[iid] = str(p); return iid

    def _insert_node(self, parent: str, p: Path, root_node=False) -> str:
        iid = self._new_iid(p)
        self.browser_tree.insert(parent, tk.END, iid=iid, text=str(p) if root_node else p.name, values=(self._kind(p), self._size(p)))
        if p.is_dir(): self.browser_tree.insert(iid, tk.END, text="â€¦")
        return iid

    def _load_children(self, iid: str):
        if iid in self.browser_loaded: return
        p = Path(self.browser_paths.get(iid, ""))
        if not p.is_dir(): return
        for c in self.browser_tree.get_children(iid): self.browser_tree.delete(c)
        dirs, files = [], []
        try:
            for e in os.scandir(p):
                ep = Path(e.path)
                if e.is_dir(follow_symlinks=False): dirs.append(ep)
                elif e.is_file(follow_symlinks=False) and self._visible_file(ep): files.append(ep)
        except Exception:
            return
        for d in sorted(dirs, key=lambda x: x.name.lower()): self._insert_node(iid, d)
        for f in sorted(files, key=lambda x: (x.suffix.lower(), x.name.lower())): self._insert_node(iid, f)
        self.browser_loaded.add(iid)

    def on_browser_open(self, event=None):
        self._load_children(self.browser_tree.focus())

    def _selected_path(self) -> Path | None:
        sel = self.browser_tree.selection()
        return Path(self.browser_paths[sel[0]]) if sel and sel[0] in self.browser_paths else None

    def on_browser_select(self, event=None):
        p = self._selected_path()
        if not p: return
        lines = [f"Pfad: {p}", f"Typ: {self._kind(p)}", f"GrÃ¶ÃŸe: {self._size(p)}"]
        try:
            if p.suffix.lower() == ".texture":
                desc = parse_texture_descriptor(p); lines += ["", f"AuflÃ¶sung: {desc['width']}x{desc['height']}", f"Mips: {desc['mips']}", f"Tile: {desc['tile_w']}x{desc['tile_h']}"]
                mips = p.with_suffix(".texturemips")
                if mips.exists():
                    fmt, note = choose_auto_format(p, desc, mips.stat().st_size)
                    lines += [f".texturemips: gefunden", f"Format-Vorschlag: {fmt}", note]
            elif p.suffix.lower() == ".dds":
                dds = parse_dds(p); lines += ["", f"DDS: {dds['width']}x{dds['height']} / {dds['mips']} mips", f"DXGI: {dds['dxgi_format']} ({DXGI_TO_NAME.get(dds['dxgi_format'], '')})"]
            elif p.suffix.lower() == ".actor":
                cards = actor_list_cardesigns(p.read_bytes())
                lines += ["", ".actor Datei", f"Registrierte Cardesigns: {len(cards)}"]
                for card in cards[:40]:
                    lines.append(f"- {card}")
                if len(cards) > 40:
                    lines.append(f"... plus {len(cards) - 40} weitere")
            elif p.suffix.lower() == ".visualcarpreset":
                vp = extract_visualpreset_info(p.read_bytes())
                lines += ["", ".visualcarpreset Datei", f"Name: {vp.get('name')}", f"Cardesign: {vp.get('cardesign')}", f"Rim Design: {vp.get('rim_design')}"]
            elif p.suffix.lower() == ".mechanicalcarpreset":
                strings = collect_proto_strings(p.read_bytes())
                refs = [v for _path, v in strings if "visualcarpreset" in v.lower() or "compatiblerim" in v.lower()]
                lines += ["", ".mechanicalcarpreset Datei"] + [f"Ref: {r}" for r in refs]
            elif p.suffix.lower() == ".cardesign":
                design_rel, rim_rel = extract_cardesign_info(p.read_bytes())
                lines += ["", ".cardesign Datei", f"Design: {design_rel}", f"Compatible Rim: {rim_rel}"]
            elif p.suffix.lower() == ".design":
                dd = extract_design_details(p.read_bytes(), root=Path(self.root_var.get().strip()) if self.root_var.get().strip() else None)
                lines += ["", ".design Datei", f"Display Name: {dd.get('display_name')}", f"Manufacturer: {dd.get('manufacturer')}", f"Slots: {len(dd.get('slots', []))}"]
                for slot in dd.get("slots", [])[:8]:
                    lines.append(f"- {slot.get('slot_id')} â†’ {slot.get('material')}")
        except Exception as exc:
            lines += ["", f"Info Fehler: {exc}"]
        self.detail.delete("1.0", tk.END); self.detail.insert("1.0", "\n".join(lines))

    def on_browser_double(self, event=None):
        p = self._selected_path()
        if not p: return
        if p.is_dir(): self._load_children(self.browser_tree.focus()); return
        if p.suffix.lower() == ".texture": self.browser_use_texture()
        elif p.suffix.lower() == ".material": self.browser_use_material()
        elif p.suffix.lower() == ".actor": self.browser_use_actor()
        elif p.suffix.lower() in {".cardesign", ".design"}: self.browser_view_design_file()
        elif p.suffix.lower() == ".visualcarpreset":
            self.skin_visual_var.set(str(p)); self.notebook.select(self.tab_skin); self.write(f"Visual-Preset Vorlage Ã¼bernommen: {p}")
        elif p.suffix.lower() == ".mechanicalcarpreset":
            self.skin_mechanical_var.set(str(p)); self.notebook.select(self.tab_skin); self.write(f"Mechanical-Preset Vorlage Ã¼bernommen: {p}")

    def browser_use_texture(self):
        p = self._selected_path()
        if not p or p.suffix.lower() != ".texture": messagebox.showwarning("Keine .texture", "Bitte .texture auswÃ¤hlen."); return
        self.texture_var.set(str(p)); self.mips_var.set(str(p.with_suffix(".texturemips"))); self.dds_out_var.set(str(p.with_suffix(".auto.dds"))); self.notebook.select(self.tab_convert)

    def browser_use_material(self):
        p = self._selected_path()
        if not p or p.suffix.lower() != ".material": messagebox.showwarning("Keine .material", "Bitte .material auswÃ¤hlen."); return
        self.material_var.set(str(p)); self.notebook.select(self.tab_material); self.inspect_material_action()

    def browser_use_skin_template(self):
        p = self._selected_path()
        if not p: return
        if p.suffix.lower() == ".cardesign":
            self.skin_cardesign_var.set(str(p))
            try:
                design_rel, _ = extract_cardesign_info(p.read_bytes()); name = Path(design_rel.replace("\\", "/")).name if design_rel else ""
                cand = p.parent / name
                if cand.exists(): self.skin_design_var.set(str(cand))
            except Exception: pass
        elif p.suffix.lower() == ".design":
            self.skin_design_var.set(str(p))
        self.notebook.select(self.tab_skin)

    def browser_view_design_file(self):
        p = self._selected_path()
        if not p or p.suffix.lower() not in {".cardesign", ".design"}:
            messagebox.showwarning("Keine .cardesign/.design", "Bitte .cardesign oder .design Datei auswÃ¤hlen."); return
        if p.suffix.lower() == ".cardesign":
            self.design_view_cardesign_var.set(str(p))
            try:
                design_rel, _ = extract_cardesign_info(p.read_bytes())
                if design_rel:
                    root = Path(self.root_var.get().strip()) if self.root_var.get().strip() else p.parent
                    dpath = resolve_content_path(root, design_rel)
                    if dpath.exists():
                        self.design_view_design_var.set(str(dpath))
            except Exception:
                pass
        else:
            self.design_view_design_var.set(str(p))
        self.notebook.select(self.tab_design)
        self.design_load_action()

    def browser_use_actor(self):
        p = self._selected_path()
        if not p or p.suffix.lower() != ".actor":
            messagebox.showwarning("Keine .actor", "Bitte .actor Datei auswÃ¤hlen."); return
        self.actor_viewer_var.set(str(p))
        self.skin_actor_var.set(str(p))
        self.notebook.select(self.tab_actor)
        self.actor_load_action()

    def browser_open_explorer(self):
        p = self._selected_path()
        if not p: return
        target = p if p.is_dir() else p.parent
        try:
            if sys.platform.startswith("win"): os.startfile(str(target))  # type: ignore[attr-defined]
            elif sys.platform == "darwin": subprocess.Popen(["open", str(target)])
            else: subprocess.Popen(["xdg-open", str(target)])
        except Exception as exc: messagebox.showerror("Fehler", str(exc))

    # Converter tab -----------------------------------------------------------
    def _build_convert_tab(self):
        left = ttk.LabelFrame(self.tab_convert, text=".texturemips â†’ DDS", padding=8); left.pack(fill=tk.X, pady=(0,8))
        self._row(left, ".texture", self.texture_var, lambda: self.pick_file(self.texture_var, [("Texture", "*.texture")]), "Datei")
        self._row(left, ".texturemips", self.mips_var, lambda: self.pick_file(self.mips_var, [("Mips", "*.texturemips")]), "Datei")
        self._row(left, "DDS Ziel", self.dds_out_var, lambda: self.pick_save(self.dds_out_var, ".dds"), "Speichern")
        row = ttk.Frame(left); row.pack(fill=tk.X, pady=3)
        ttk.Label(row, text="Format", width=18).pack(side=tk.LEFT)
        ttk.Combobox(row, textvariable=self.dds_format_var, values=FORMAT_CHOICES, state="readonly", width=18).pack(side=tk.LEFT)
        ttk.Button(row, text="Als DDS exportieren", command=self.export_dds_action).pack(side=tk.LEFT, padx=8)
        right = ttk.LabelFrame(self.tab_convert, text="DDS â†’ .texturemips", padding=8); right.pack(fill=tk.X, pady=(0,8))
        self._row(right, "Original .texture", self.back_texture_var, lambda: self.pick_file(self.back_texture_var, [("Texture", "*.texture")]), "Datei")
        self._row(right, "Bearbeitete DDS", self.dds_in_var, lambda: self.pick_file(self.dds_in_var, [("DDS", "*.dds")]), "Datei")
        self._row(right, ".texturemips Ziel", self.mips_out_var, lambda: self.pick_save(self.mips_out_var, ".texturemips"), "Speichern")
        ttk.Button(right, text="ZurÃ¼ck zu .texturemips konvertieren", command=self.import_mips_action).pack(anchor="w", pady=6)

    def pick_file(self, var: tk.StringVar, filetypes):
        p = filedialog.askopenfilename(filetypes=filetypes + [("All files", "*.*")])
        if p: var.set(p)

    def pick_save(self, var: tk.StringVar, ext: str):
        p = filedialog.asksaveasfilename(defaultextension=ext, filetypes=[(ext.upper().strip('.'), f"*{ext}"), ("All files", "*.*")])
        if p: var.set(p)

    def pick_dir(self, var: tk.StringVar):
        p = filedialog.askdirectory()
        if p: var.set(p)

    def export_dds_action(self):
        tex = self.texture_var.get().strip(); mips = self.mips_var.get().strip() or None; out = self.dds_out_var.get().strip()
        if not tex or not out: messagebox.showwarning("Fehlt", "Bitte .texture und Ziel-DDS wÃ¤hlen."); return
        self._run_threaded(lambda: convert_to_dds(tex, mips, out, self.dds_format_var.get()))

    def import_mips_action(self):
        tex = self.back_texture_var.get().strip(); dds = self.dds_in_var.get().strip(); out = self.mips_out_var.get().strip()
        if not tex or not dds or not out: messagebox.showwarning("Fehlt", "Bitte .texture, DDS und Ziel-.texturemips wÃ¤hlen."); return
        self._run_threaded(lambda: convert_to_texturemips(tex, dds, out))

    # Material tab ------------------------------------------------------------
    def _build_material_tab(self):
        self._row(self.tab_material, ".material", self.material_var, lambda: self.pick_file(self.material_var, [("Material", "*.material")]), "Datei")
        self._row(self.tab_material, "Report Ordner", self.material_report_dir_var, lambda: self.pick_dir(self.material_report_dir_var), "Ordner")
        self._row(self.tab_material, "DDS Export Ordner", self.material_export_dir_var, lambda: self.pick_dir(self.material_export_dir_var), "Ordner")
        btns = ttk.Frame(self.tab_material); btns.pack(fill=tk.X, pady=6)
        ttk.Button(btns, text="Material analysieren", command=self.inspect_material_action).pack(side=tk.LEFT)
        ttk.Button(btns, text="Reports speichern", command=self.material_reports_action).pack(side=tk.LEFT, padx=6)
        ttk.Button(btns, text="Referenzierte Texturen als DDS exportieren", command=self.material_export_textures_action).pack(side=tk.LEFT)
        pane = ttk.PanedWindow(self.tab_material, orient=tk.VERTICAL); pane.pack(fill=tk.BOTH, expand=True, pady=8)
        f1 = ttk.LabelFrame(pane, text="Texture Slots", padding=5); f2 = ttk.LabelFrame(pane, text="Parameter", padding=5)
        pane.add(f1, weight=3); pane.add(f2, weight=2)
        self.mat_slots = ttk.Treeview(f1, columns=("slot", "fmt", "size", "ref", "local"), show="headings")
        for col, title, w in [("slot","Slot",170),("fmt","Format",90),("size","GrÃ¶ÃŸe",120),("ref","Referenz",400),("local","Lokal",250)]:
            self.mat_slots.heading(col, text=title); self.mat_slots.column(col, width=w)
        self.mat_slots.pack(fill=tk.BOTH, expand=True)
        self.mat_params = ttk.Treeview(f2, columns=("name", "value", "cat"), show="headings")
        for col, title, w in [("name","Parameter",300),("value","Wert",300),("cat","Kategorie",120)]:
            self.mat_params.heading(col, text=title); self.mat_params.column(col, width=w)
        self.mat_params.pack(fill=tk.BOTH, expand=True)

    def inspect_material_action(self):
        p = self.material_var.get().strip()
        if not p: messagebox.showwarning("Fehlt", "Bitte .material wÃ¤hlen."); return
        root = self.root_var.get().strip() or None
        def work():
            result = inspect_material(p, root=root)
            self.material_result = result
            self.after(0, lambda: self.populate_material(result))
            return material_summary(result)
        self._run_threaded(work)

    def populate_material(self, result):
        for t in [self.mat_slots, self.mat_params]:
            for item in t.get_children(): t.delete(item)
        for slot in result.get("texture_slots", []):
            if not slot.get("is_referenced") and not slot.get("value"): continue
            size = f"{slot.get('width')}x{slot.get('height')} / {slot.get('mips')}" if slot.get("width") else ""
            local = Path(str(slot.get("resolved_path"))).name if slot.get("resolved_path") else ""
            self.mat_slots.insert("", tk.END, values=(slot.get("name"), slot.get("suggested_format") or "", size, slot.get("value") or "", local))
        for p in result.get("parameters", []):
            if p.get("value") not in (None, [], ""):
                self.mat_params.insert("", tk.END, values=(p.get("name"), format_value(p.get("value")), classify_parameter(str(p.get("name") or ""), p.get("value"))))

    def material_reports_action(self):
        if self.material_result is None: self.inspect_material_action(); return
        out = self.material_report_dir_var.get().strip() or str(Path(self.material_var.get()).parent / "_material_reports")
        self._run_threaded(lambda: write_material_reports(self.material_result, Path(out)))

    def material_export_textures_action(self):
        if self.material_result is None: self.inspect_material_action(); return
        out_base = Path(self.material_export_dir_var.get().strip() or (Path(self.material_var.get()).parent / "_dds_from_material"))
        def work():
            out_base.mkdir(parents=True, exist_ok=True)
            count = 0; skipped = 0; errors = []
            for slot in self.material_result.get("texture_slots", []):
                tex = slot.get("resolved_path"); mips = slot.get("texturemips_path")
                if not tex or not mips: skipped += 1; continue
                out = out_base / f"{re.sub(r'[^A-Za-z0-9_.-]+','_', str(slot.get('name') or Path(tex).stem))}__{Path(tex).stem}.dds"
                try:
                    convert_to_dds(tex, mips, out, "auto"); count += 1
                except Exception as exc: errors.append(f"{slot.get('name')}: {exc}")
            msg = f"Export fertig: {count} DDS, {skipped} Ã¼bersprungen\nOrdner: {out_base}"
            if errors: msg += "\n" + "\n".join(errors[:10])
            return msg
        self._run_threaded(work)

    # Actor Viewer tab -------------------------------------------------------
    def _build_actor_tab(self):
        ttk.Label(self.tab_actor, text="Actor Viewer: registrierte Cardesigns / Skins anzeigen", font=("Segoe UI", 11, "bold")).pack(anchor="w", pady=(0,8))
        self._row(self.tab_actor, ".actor Datei", self.actor_viewer_var, lambda: self.pick_file(self.actor_viewer_var, [("Actor", "*.actor")]), "Datei")
        btns = ttk.Frame(self.tab_actor); btns.pack(fill=tk.X, pady=5)
        ttk.Button(btns, text="Actor laden", command=self.actor_load_action).pack(side=tk.LEFT)
        ttk.Button(btns, text="Aus Skin Creator Ã¼bernehmen", command=self.actor_from_skin_creator).pack(side=tk.LEFT, padx=8)
        ttk.Button(btns, text="In Skin Creator Ã¼bernehmen", command=self.actor_to_skin_creator).pack(side=tk.LEFT)
        ttk.Button(btns, text="Auswahl im Design Viewer Ã¶ffnen", command=self.actor_open_selected_in_design_viewer).pack(side=tk.LEFT, padx=8)
        ttk.Button(btns, text="Report speichern", command=self.actor_save_report_action).pack(side=tk.LEFT, padx=8)

        paned = ttk.PanedWindow(self.tab_actor, orient=tk.HORIZONTAL); paned.pack(fill=tk.BOTH, expand=True, pady=(8,0))
        left = ttk.LabelFrame(paned, text="Registrierte .cardesign EintrÃ¤ge", padding=5)
        right = ttk.LabelFrame(paned, text="Details", padding=5)
        paned.add(left, weight=3); paned.add(right, weight=2)

        cols = ("idx", "cardesign", "exists")
        self.actor_tree = ttk.Treeview(left, columns=cols, show="headings")
        self.actor_tree.heading("idx", text="#"); self.actor_tree.heading("cardesign", text="Cardesign-Pfad"); self.actor_tree.heading("exists", text="gefunden")
        self.actor_tree.column("idx", width=50, anchor="e"); self.actor_tree.column("cardesign", width=560, anchor="w"); self.actor_tree.column("exists", width=80, anchor="center")
        self.actor_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        sb = ttk.Scrollbar(left, orient=tk.VERTICAL, command=self.actor_tree.yview); sb.pack(side=tk.RIGHT, fill=tk.Y)
        self.actor_tree.configure(yscrollcommand=sb.set)
        self.actor_tree.bind("<<TreeviewSelect>>", self.actor_tree_select)

        self.actor_detail = tk.Text(right, wrap="word")
        self.actor_detail.pack(fill=tk.BOTH, expand=True)
        self.actor_detail.insert("1.0", "Eine .actor Datei laden, um die im Fahrzeug registrierten Skins/Cardesigns zu sehen.\n")

    def actor_from_skin_creator(self):
        if not self.skin_actor_var.get().strip():
            messagebox.showinfo("Keine .actor", "Im Skin Creator ist noch keine .actor Datei gesetzt."); return
        self.actor_viewer_var.set(self.skin_actor_var.get().strip())
        self.actor_load_action()

    def actor_to_skin_creator(self):
        if not self.actor_viewer_var.get().strip():
            messagebox.showinfo("Keine .actor", "Bitte erst eine .actor Datei laden."); return
        self.skin_actor_var.set(self.actor_viewer_var.get().strip())
        self.notebook.select(self.tab_skin)
        self.write(f"Actor in Skin Creator Ã¼bernommen: {self.actor_viewer_var.get().strip()}")

    def actor_open_selected_in_design_viewer(self):
        sel = self.actor_tree.selection()
        if not sel:
            messagebox.showinfo("Keine Auswahl", "Bitte zuerst einen Cardesign-Eintrag auswÃ¤hlen."); return
        vals = self.actor_tree.item(sel[0], "values")
        if not vals or len(vals) < 2:
            return
        resolved = self._actor_resolve_cardesign(vals[1])
        if not resolved.exists():
            messagebox.showwarning("Nicht gefunden", f"Cardesign wurde lokal nicht gefunden:\n{resolved}"); return
        self.design_view_cardesign_var.set(str(resolved))
        try:
            design_rel, _ = extract_cardesign_info(resolved.read_bytes())
            if design_rel:
                root = Path(self.root_var.get().strip()) if self.root_var.get().strip() else resolved.parent
                dpath = resolve_content_path(root, design_rel)
                if dpath.exists(): self.design_view_design_var.set(str(dpath))
        except Exception:
            pass
        self.notebook.select(self.tab_design)
        self.design_load_action()

    def _actor_resolve_cardesign(self, cardesign_rel: str) -> Path:
        root = Path(self.root_var.get().strip()) if self.root_var.get().strip() else Path(self.actor_viewer_var.get()).parent
        return resolve_content_path(root, cardesign_rel)

    def actor_load_action(self):
        p = Path(self.actor_viewer_var.get().strip())
        if not p.exists() or p.suffix.lower() != ".actor":
            messagebox.showwarning("Keine .actor", "Bitte eine gÃ¼ltige .actor Datei wÃ¤hlen."); return
        for item in self.actor_tree.get_children(): self.actor_tree.delete(item)
        try:
            cards = actor_list_cardesigns(p.read_bytes())
            for idx, card in enumerate(cards, start=1):
                resolved = self._actor_resolve_cardesign(card)
                self.actor_tree.insert("", tk.END, values=(idx, card, "ja" if resolved.exists() else "nein"))
            text = [f"Actor: {p}", f"Registrierte Cardesigns: {len(cards)}"]
            if not cards:
                text.append("Keine .cardesign Referenzen gefunden. Eventuell hat die Datei eine andere Struktur.")
            self.actor_detail.delete("1.0", tk.END); self.actor_detail.insert("1.0", "\n".join(text))
            self.write(f"Actor geladen: {p} ({len(cards)} Cardesigns)")
        except Exception as exc:
            messagebox.showerror("Actor Fehler", str(exc))

    def actor_tree_select(self, event=None):
        sel = self.actor_tree.selection()
        if not sel: return
        vals = self.actor_tree.item(sel[0], "values")
        if not vals or len(vals) < 2: return
        card = vals[1]
        lines = [f"Cardesign: {card}"]
        resolved = self._actor_resolve_cardesign(card)
        lines.append(f"Lokale Datei: {resolved}")
        lines.append(f"Gefunden: {'ja' if resolved.exists() else 'nein'}")
        if resolved.exists():
            try:
                design_rel, rim_rel = extract_cardesign_info(resolved.read_bytes())
                lines += ["", f"Design: {design_rel}", f"Compatible Rim: {rim_rel}"]
                design_path = resolve_content_path(Path(self.root_var.get().strip()) if self.root_var.get().strip() else resolved.parent, design_rel) if design_rel else None
                if design_path and design_path.exists():
                    info = extract_design_info(resolved.read_bytes(), design_path.read_bytes())
                    lines += [f"Design-Datei: {design_path}", f"Display Name: {info.display_name}", f"EXT_SKIN Material: {info.ext_skin_material_path}"]
            except Exception as exc:
                lines.append(f"Detail-Analyse Fehler: {exc}")
        self.actor_detail.delete("1.0", tk.END); self.actor_detail.insert("1.0", "\n".join(lines))

    def actor_save_report_action(self):
        p = Path(self.actor_viewer_var.get().strip())
        if not p.exists() or p.suffix.lower() != ".actor":
            messagebox.showwarning("Keine .actor", "Bitte erst eine .actor Datei laden."); return
        out = filedialog.asksaveasfilename(title="Actor Report speichern", initialfile=p.name + "_report.txt", defaultextension=".txt", filetypes=[("Text", "*.txt"), ("All files", "*.*")])
        if not out: return
        cards = actor_list_cardesigns(p.read_bytes())
        lines = [f"Actor: {p}", f"Cardesigns: {len(cards)}", ""]
        for idx, card in enumerate(cards, start=1):
            resolved = self._actor_resolve_cardesign(card)
            lines.append(f"{idx:02d}. {card}")
            lines.append(f"    local: {resolved}")
            lines.append(f"    exists: {'yes' if resolved.exists() else 'no'}")
        backup = safe_write_text(Path(out), "\n".join(lines))
        self.write(f"Actor Report gespeichert: {out}" + (f"\nBackup: {backup}" if backup else ""))

    # Design / Cardesign Viewer tab -----------------------------------------
    def _build_design_tab(self):
        ttk.Label(self.tab_design, text="Design Viewer/Editor: .cardesign und .design ansehen und bekannte Felder editieren", font=("Segoe UI", 11, "bold")).pack(anchor="w", pady=(0,8))
        self._row(self.tab_design, ".cardesign Datei", self.design_view_cardesign_var, lambda: self.pick_file(self.design_view_cardesign_var, [("Cardesign", "*.cardesign")]), "Datei")
        self._row(self.tab_design, ".design Datei", self.design_view_design_var, lambda: self.pick_file(self.design_view_design_var, [("Design", "*.design")]), "Datei")
        btns = ttk.Frame(self.tab_design); btns.pack(fill=tk.X, pady=5)
        ttk.Button(btns, text="Laden / analysieren", command=self.design_load_action).pack(side=tk.LEFT)
        ttk.Button(btns, text="Aus Skin Creator Ã¼bernehmen", command=self.design_from_skin_creator).pack(side=tk.LEFT, padx=8)
        ttk.Button(btns, text="In Skin Creator Ã¼bernehmen", command=self.design_to_skin_creator).pack(side=tk.LEFT)
        ttk.Button(btns, text="Report speichern", command=self.design_save_report_action).pack(side=tk.LEFT, padx=8)
        ttk.Button(btns, text="Ã„nderungen speichern", command=self.design_save_edits_action).pack(side=tk.LEFT, padx=8)

        edit = ttk.LabelFrame(self.tab_design, text="Direkt editierbare Felder mit Backup", padding=8)
        edit.pack(fill=tk.X, pady=(4, 8))
        self._row(edit, "Cardesign â†’ Design", self.design_edit_cardesign_design_var, None)
        self._row(edit, "Cardesign â†’ Rim", self.design_edit_cardesign_rim_var, None)
        self._row(edit, "Design Name", self.design_edit_display_name_var, None)
        self._row(edit, "Manufacturer", self.design_edit_manufacturer_var, None)
        self._row(edit, "EXT_SKIN Material", self.design_edit_ext_skin_material_var, None)
        ttk.Label(edit, text="Speichern schreibt nur bekannte Felder und legt vorher automatisch ein Backup in _acevo_backups an.", foreground="#666").pack(anchor="w", pady=(4,0))

        paned = ttk.PanedWindow(self.tab_design, orient=tk.HORIZONTAL); paned.pack(fill=tk.BOTH, expand=True, pady=(8,0))
        left = ttk.LabelFrame(paned, text="Struktur", padding=5)
        right = ttk.LabelFrame(paned, text="Details", padding=5)
        paned.add(left, weight=3); paned.add(right, weight=2)

        cols = ("kind", "name", "value", "exists")
        self.design_tree = ttk.Treeview(left, columns=cols, show="headings")
        for col, title, w in [("kind","Typ",130),("name","Name",220),("value","Wert / Pfad",560),("exists","gefunden",80)]:
            self.design_tree.heading(col, text=title); self.design_tree.column(col, width=w)
        self.design_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        sb = ttk.Scrollbar(left, orient=tk.VERTICAL, command=self.design_tree.yview); sb.pack(side=tk.RIGHT, fill=tk.Y)
        self.design_tree.configure(yscrollcommand=sb.set)
        self.design_tree.bind("<<TreeviewSelect>>", self.design_tree_select)

        self.design_detail = tk.Text(right, wrap="word")
        self.design_detail.pack(fill=tk.BOTH, expand=True)
        self.design_detail.insert("1.0", "Eine .cardesign oder .design Datei laden, um Designpfade, Rim, Slots, Materialien und Farboptionen zu sehen.\n")

    def _design_root(self) -> Path | None:
        return Path(self.root_var.get().strip()) if self.root_var.get().strip() else None

    def _design_resolve(self, rel: str | None, fallback: Path | None = None) -> Path | None:
        if not rel:
            return None
        root = self._design_root() or fallback
        if root is None:
            return None
        return resolve_content_path(root, rel)

    def _design_insert(self, kind: str, name: str, value: object, exists: str = "", detail: str = ""):
        iid = self.design_tree.insert("", tk.END, values=(kind, name, value if value is not None else "", exists))
        self.design_detail_map[iid] = detail or f"{kind}\n{name}\n{value}"
        return iid

    def design_load_action(self):
        for item in self.design_tree.get_children(): self.design_tree.delete(item)
        self.design_detail_map.clear()
        cardesign_path = Path(self.design_view_cardesign_var.get().strip()) if self.design_view_cardesign_var.get().strip() else None
        design_path = Path(self.design_view_design_var.get().strip()) if self.design_view_design_var.get().strip() else None
        root = self._design_root()

        if cardesign_path and cardesign_path.exists():
            cd = extract_cardesign_details(cardesign_path.read_bytes(), root=root or cardesign_path.parent)
            self._design_insert("Cardesign", "Datei", str(cardesign_path), "ja", f"Cardesign-Datei:\n{cardesign_path}")
            drel = cd.get("design_path")
            rrel = cd.get("rim_path")
            dlocal = self._design_resolve(str(drel), cardesign_path.parent) if drel else None
            rlocal = self._design_resolve(str(rrel), cardesign_path.parent) if rrel else None
            self._design_insert("Cardesign", "Design", drel, "ja" if dlocal and dlocal.exists() else "nein", f"Design-Referenz:\n{drel}\n\nLokal:\n{dlocal}")
            self._design_insert("Cardesign", "Compatible Rim", rrel, "ja" if rlocal and rlocal.exists() else "nein", f"Rim-Referenz:\n{rrel}\n\nLokal:\n{rlocal}")
            self.design_edit_cardesign_design_var.set(str(drel or ""))
            self.design_edit_cardesign_rim_var.set(str(rrel or ""))
            if (not design_path or not design_path.exists()) and dlocal and dlocal.exists():
                self.design_view_design_var.set(str(dlocal))
                design_path = dlocal

        if design_path and design_path.exists():
            dd = extract_design_details(design_path.read_bytes(), root=root or design_path.parent)
            self._design_insert("Design", "Datei", str(design_path), "ja", f"Design-Datei:\n{design_path}")
            self._design_insert("Design", "Sichtbarer Name", dd.get("display_name"), "", f"Display Name:\n{dd.get('display_name')}")
            self._design_insert("Design", "Manufacturer", dd.get("manufacturer"), "", f"Manufacturer / Brand:\n{dd.get('manufacturer')}")
            self.design_edit_display_name_var.set(str(dd.get("display_name") or ""))
            self.design_edit_manufacturer_var.set(str(dd.get("manufacturer") or ""))
            ext_mat_for_edit = ""
            for sslot in dd.get("slots", []):
                if sslot.get("slot_id") == "EXT_SKIN" and sslot.get("material"):
                    ext_mat_for_edit = str(sslot.get("material"))
                    break
            self.design_edit_ext_skin_material_var.set(ext_mat_for_edit)
            for idx, slot in enumerate(dd.get("slots", []), start=1):
                sid = slot.get("slot_id") or f"Slot {idx}"
                sname = slot.get("slot_name") or ""
                material = slot.get("material")
                mlocal = Path(str(slot.get("material_local"))) if slot.get("material_local") else None
                detail = [f"Slot {idx}", f"ID: {sid}", f"Name: {sname}", f"Material: {material}"]
                if mlocal: detail += [f"Material local: {mlocal}", f"Material exists: {'yes' if mlocal.exists() else 'no'}"]
                if slot.get("color_table"): detail.append(f"Color table: {slot.get('color_table')}")
                if slot.get("color_table_local"): detail.append(f"Color table local: {slot.get('color_table_local')}")
                colors = slot.get("colors") or []
                if colors: detail += ["", "Colors:"] + [f"- {c}" for c in colors]
                opts = slot.get("option_refs") or []
                if opts: detail += ["", "Options:"] + [f"- {o}" for o in opts]
                extra = slot.get("extra_strings") or []
                if extra: detail += ["", "Weitere Strings:"] + [f"- {p}: {v}" for p, v in extra]
                self._design_insert("Slot", f"{sid} / {sname}", material, "ja" if mlocal and mlocal.exists() else ("nein" if material else ""), "\n".join(detail))
                if slot.get("color_table"):
                    clocal = Path(str(slot.get("color_table_local"))) if slot.get("color_table_local") else None
                    self._design_insert("Color Table", str(sid), slot.get("color_table"), "ja" if clocal and clocal.exists() else "nein", f"Color table fÃ¼r {sid}:\n{slot.get('color_table')}\n\nLokal:\n{clocal}")
                for opt in opts:
                    olocal = self._design_resolve(str(opt), design_path.parent)
                    self._design_insert("Option", str(sid), opt, "ja" if olocal and olocal.exists() else "nein", f"Option fÃ¼r {sid}:\n{opt}\n\nLokal:\n{olocal}")
        if not self.design_tree.get_children():
            messagebox.showwarning("Keine Datei", "Bitte eine gÃ¼ltige .cardesign oder .design Datei wÃ¤hlen.")
            return
        self.write("Design/Cardesign geladen")

    def design_tree_select(self, event=None):
        sel = self.design_tree.selection()
        if not sel: return
        detail = self.design_detail_map.get(sel[0], "")
        self.design_detail.delete("1.0", tk.END); self.design_detail.insert("1.0", detail)

    def design_from_skin_creator(self):
        if self.skin_cardesign_var.get().strip(): self.design_view_cardesign_var.set(self.skin_cardesign_var.get().strip())
        if self.skin_design_var.get().strip(): self.design_view_design_var.set(self.skin_design_var.get().strip())
        self.design_load_action()

    def design_to_skin_creator(self):
        if self.design_view_cardesign_var.get().strip(): self.skin_cardesign_var.set(self.design_view_cardesign_var.get().strip())
        if self.design_view_design_var.get().strip(): self.skin_design_var.set(self.design_view_design_var.get().strip())
        self.notebook.select(self.tab_skin)
        self.write("Cardesign/Design in Skin Creator Ã¼bernommen")

    def design_save_edits_action(self):
        cardesign_path = Path(self.design_view_cardesign_var.get().strip()) if self.design_view_cardesign_var.get().strip() else None
        design_path = Path(self.design_view_design_var.get().strip()) if self.design_view_design_var.get().strip() else None
        changed: list[str] = []
        backups: list[str] = []
        try:
            if cardesign_path and cardesign_path.exists():
                old_data = cardesign_path.read_bytes()
                new_design_rel = self.design_edit_cardesign_design_var.get().strip()
                new_rim_rel = self.design_edit_cardesign_rim_var.get().strip()
                new_data = edit_cardesign_data(old_data, design_path=new_design_rel if new_design_rel else None, rim_path=new_rim_rel if new_rim_rel else None)
                if new_data != old_data:
                    backup = safe_write_bytes(cardesign_path, new_data)
                    changed.append(str(cardesign_path))
                    if backup: backups.append(str(backup))
            if design_path and design_path.exists():
                old_data = design_path.read_bytes()
                new_name = self.design_edit_display_name_var.get().strip()
                new_manufacturer = self.design_edit_manufacturer_var.get().strip()
                new_ext_mat = self.design_edit_ext_skin_material_var.get().strip()
                new_data = edit_design_data(old_data, display_name=new_name if new_name else None, manufacturer=new_manufacturer if new_manufacturer else None, ext_skin_material=new_ext_mat if new_ext_mat else None)
                if new_data != old_data:
                    backup = safe_write_bytes(design_path, new_data)
                    changed.append(str(design_path))
                    if backup: backups.append(str(backup))
        except Exception as e:
            messagebox.showerror("Fehler beim Speichern", str(e))
            return
        if not changed:
            messagebox.showinfo("Keine Ã„nderung", "Es wurden keine Ã„nderungen erkannt.")
            return
        self.design_load_action()
        msg = "Gespeichert:\n" + "\n".join(changed)
        if backups:
            msg += "\n\nBackups:\n" + "\n".join(backups)
        self.write(msg)

    def design_save_report_action(self):
        c = Path(self.design_view_cardesign_var.get().strip()) if self.design_view_cardesign_var.get().strip() else None
        d = Path(self.design_view_design_var.get().strip()) if self.design_view_design_var.get().strip() else None
        if (not c or not c.exists()) and (not d or not d.exists()):
            messagebox.showwarning("Keine Datei", "Bitte zuerst .cardesign oder .design laden."); return
        initial = (c.name if c and c.exists() else d.name) + "_report.txt"
        out = filedialog.asksaveasfilename(title="Design Report speichern", initialfile=initial, defaultextension=".txt", filetypes=[("Text", "*.txt"), ("All files", "*.*")])
        if not out: return
        text = design_cardesign_report(c if c and c.exists() else None, d if d and d.exists() else None, root=self._design_root())
        backup = safe_write_text(Path(out), text)
        self.write(f"Design Report gespeichert: {out}" + (f"\nBackup: {backup}" if backup else ""))

    # Skin Creator tab --------------------------------------------------------
    def _build_skin_tab(self):
        ttk.Label(self.tab_skin, text="Neuen Skin aus vorhandener .cardesign/.design Vorlage erstellen", font=("Segoe UI", 11, "bold")).pack(anchor="w", pady=(0,8))
        self._row(self.tab_skin, ".cardesign Vorlage", self.skin_cardesign_var, lambda: self.pick_file(self.skin_cardesign_var, [("Cardesign", "*.cardesign")]), "Datei")
        self._row(self.tab_skin, ".design Vorlage", self.skin_design_var, lambda: self.pick_file(self.skin_design_var, [("Design", "*.design")]), "Datei")
        self._row(self.tab_skin, ".actor Datei", self.skin_actor_var, lambda: self.pick_file(self.skin_actor_var, [("Actor", "*.actor")]), "Datei")
        self._row(self.tab_skin, ".visualcarpreset Vorlage", self.skin_visual_var, lambda: self.pick_file(self.skin_visual_var, [("Visual Preset", "*.visualcarpreset")]), "Datei")
        self._row(self.tab_skin, ".mechanicalcarpreset Datei", self.skin_mechanical_var, lambda: self.pick_file(self.skin_mechanical_var, [("Mechanical Preset", "*.mechanicalcarpreset")]), "Datei")
        form = ttk.LabelFrame(self.tab_skin, text="Neuer Skin", padding=8); form.pack(fill=tk.X, pady=8)
        self._row(form, "Skin ID / Ordner", self.skin_id_var, None)
        self._row(form, "Sichtbarer Name", self.skin_name_var, None)
        opts = ttk.Frame(form); opts.pack(fill=tk.X, pady=4)
        ttk.Checkbutton(opts, text="Quell-Skin-Ordner kopieren", variable=self.skin_copy_source_var).pack(side=tk.LEFT)
        ttk.Checkbutton(opts, text=".actor registrieren", variable=self.skin_patch_actor_var).pack(side=tk.LEFT, padx=18)
        ttk.Checkbutton(opts, text="Visual Preset erstellen", variable=self.skin_create_visual_var).pack(side=tk.LEFT, padx=18)
        ttk.Checkbutton(opts, text="Visual Preset in .actor registrieren", variable=self.skin_patch_visual_actor_var).pack(side=tk.LEFT, padx=18)
        ttk.Checkbutton(opts, text="Mechanical Preset aktualisieren", variable=self.skin_create_mechanical_var).pack(side=tk.LEFT, padx=18)
        ttk.Checkbutton(opts, text="Ãœberschreiben erlauben", variable=self.skin_overwrite_var).pack(side=tk.LEFT)
        btns = ttk.Frame(self.tab_skin); btns.pack(fill=tk.X, pady=6)
        ttk.Button(btns, text="Vorlage analysieren", command=self.skin_analyze_action).pack(side=tk.LEFT)
        ttk.Button(btns, text="Skin erstellen", command=self.skin_create_action).pack(side=tk.LEFT, padx=8)
        ttk.Button(btns, text=".actor anzeigen", command=lambda: (self.actor_viewer_var.set(self.skin_actor_var.get()), self.notebook.select(self.tab_actor), self.actor_load_action()) if self.skin_actor_var.get().strip() else messagebox.showinfo("Keine .actor", "Bitte zuerst eine .actor Datei setzen oder Vorlage analysieren.")).pack(side=tk.LEFT)
        ttk.Button(btns, text="Beispielnamen", command=lambda: (self.skin_id_var.set("mikesch_racing_2026"), self.skin_name_var.set("Mikesch Racing 2026"))).pack(side=tk.LEFT, padx=8)
        self.skin_out = tk.Text(self.tab_skin, wrap="word")
        self.skin_out.pack(fill=tk.BOTH, expand=True, pady=(8,0))
        self.skin_out.insert("1.0", "Root-Ordner im Datei-Browser setzen oder oben speichern. Dann .cardesign/.design Vorlage wÃ¤hlen.\n")

    def _skin_write(self, text):
        self.skin_out.insert(tk.END, text + "\n"); self.skin_out.see(tk.END)

    def skin_analyze_action(self):
        c = self.skin_cardesign_var.get().strip(); d = self.skin_design_var.get().strip()
        if not c or not d: messagebox.showwarning("Fehlt", "Bitte .cardesign und .design wÃ¤hlen."); return
        root = Path(self.root_var.get()) if self.root_var.get().strip() else None
        def work():
            txt = analyze_skin_template(Path(c), Path(d), root)
            try:
                if root:
                    info = extract_design_info(Path(c).read_bytes(), Path(d).read_bytes())
                    actor = find_actor_file(root, info.car_content_path)
                    if actor: self.after(0, lambda: self.skin_actor_var.set(str(actor)))
                    visual = find_visualpreset_for_cardesign(root, actor, Path(c))
                    if visual:
                        self.after(0, lambda: self.skin_visual_var.set(str(visual)))
                        mech = find_mechanicalpreset_for_visual(root, actor, visual)
                        if mech: self.after(0, lambda: self.skin_mechanical_var.set(str(mech)))
            except Exception: pass
            self.after(0, lambda: self._skin_write("\n--- Analyse ---\n" + txt))
            return "Skin-Vorlage analysiert"
        self._run_threaded(work)

    def skin_create_action(self):
        root = self.root_var.get().strip(); c = self.skin_cardesign_var.get().strip(); d = self.skin_design_var.get().strip()
        if not root or not c or not d: messagebox.showwarning("Fehlt", "Bitte Root, .cardesign und .design wÃ¤hlen."); return
        def work():
            res = create_skin(Path(root), Path(c), Path(d), self.skin_id_var.get(), self.skin_name_var.get(), overwrite=self.skin_overwrite_var.get(), copy_source_skin=self.skin_copy_source_var.get(), patch_actor=self.skin_patch_actor_var.get(), actor_file=Path(self.skin_actor_var.get()) if self.skin_actor_var.get().strip() else None, create_visual_preset=self.skin_create_visual_var.get(), visual_template=Path(self.skin_visual_var.get()) if self.skin_visual_var.get().strip() else None, patch_actor_visual=self.skin_patch_visual_actor_var.get(), update_mechanical_preset=self.skin_create_mechanical_var.get(), mechanical_template=Path(self.skin_mechanical_var.get()) if self.skin_mechanical_var.get().strip() else None, patch_actor_mechanical=self.skin_patch_mechanical_actor_var.get())
            lines = ["--- Neuer Skin erstellt ---", f"cardesign: {res.cardesign_path}", f"design:     {res.design_path}", f"visual preset: {res.visual_preset_path or '-'}", f"visual template: {res.visual_template_path or '-'}", f"mechanical preset aktualisiert: {res.mechanical_preset_path or '-'}", f"mechanical datei: {res.mechanical_template_path or '-'}", f"skin folder: {res.skin_folder}", f"material:    {res.material_path}", f"kopierte Dateien: {len(res.copied_files)}", f"gepatchte Dateien: {len(res.patched_files)}", f"actor: {res.actor_path}", f"actor cardesign patch: {'ja' if res.actor_patched else 'war bereits eingetragen/kein Patch'}", f"actor visual patch: {'ja' if res.actor_visual_patched else 'war bereits eingetragen/kein Patch'}", f"mechanical update: {'ja' if res.mechanical_updated else 'war bereits eingetragen/kein Patch'}", f"mechanical backup: {res.mechanical_backup or '-'}", f"actor backup cardesign: {res.actor_backup or '-'}", f"actor backup visual: {res.actor_visual_backup or '-'}", f"actor backup mechanical: {res.actor_mechanical_backup or '-'}"]
            self.after(0, lambda: self._skin_write("\n" + "\n".join(lines)))
            self.after(0, self.refresh_browser_tree)
            return "Neuer Skin wurde erstellt/registriert. Bitte im Spiel/Showroom testen."
        self._run_threaded(work)

    # Help --------------------------------------------------------------------
    def _build_help_tab(self):
        txt = tk.Text(self.tab_help, wrap="word")
        txt.pack(fill=tk.BOTH, expand=True)
        txt.insert("1.0", """Workflow:

1. Datei-Browser: Root-Ordner deines extrahierten AC-EVO-Contents wÃ¤hlen und als Standard speichern.
2. TextureMips / DDS: .texture + .texturemips als DDS exportieren, bearbeiten, wieder zurÃ¼ck in .texturemips konvertieren.
3. Material Inspector: .material analysieren und referenzierte Texturen direkt exportieren.
4. Actor Viewer: .actor Datei laden und prÃ¼fen, welche .cardesign/Skins registriert sind.
5. Design Viewer/Editor: .cardesign/.design Dateien laden, bekannte Felder editieren und mit Backup speichern.
6. Skin Creator: bestehende .cardesign/.design Vorlage wÃ¤hlen, optional passende .visualcarpreset Vorlage nutzen, Skin ID und sichtbaren Namen setzen, neuen Skin erstellen, Visual Preset erzeugen und die bestehende gemeinsame .mechanicalcarpreset um dieses Visual Preset erweitern.

Backups:
Vor jedem Ãœberschreiben wird automatisch eine Sicherung in _acevo_backups neben der Datei angelegt.

Hinweis:
Bitte immer in einer Kopie des extrahierten Contents testen.
""")
        txt.configure(state="disabled")


def main() -> int:
    if not TK_AVAILABLE:
        raise RuntimeError("Tkinter is not available in this Python environment. Use run_webview.py for the pywebview UI.")
    app = ToolApp()
    app.mainloop()
    return 0

if __name__ == "__main__":
    raise SystemExit(main())
