#!/usr/bin/env python3
"""
Generate the Yorvana sample vault.
Run from any directory:  python3 sample-vault/generate.py
Output:                  sample-vault/Yorvana/
"""

import json
import os
import zipfile

# ---------------------------------------------------------------------------
# Destination root
# ---------------------------------------------------------------------------
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(SCRIPT_DIR, "Yorvana")


def write(path: str, data: dict | str) -> None:
    os.makedirs(os.path.dirname(path), exist_ok=True)
    with open(path, "w") as f:
        if isinstance(data, str):
            f.write(data)
        else:
            json.dump(data, f, indent=2)
            f.write("\n")


# ---------------------------------------------------------------------------
# Categories
# ---------------------------------------------------------------------------
CUSTOM_CATEGORIES = [
    {"id": "550e8400-e29b-41d4-a716-446655440001", "label": "AC Service",        "createdAt": "2023-05-15T10:00:00Z"},
    {"id": "550e8400-e29b-41d4-a716-446655440002", "label": "Detailing",         "createdAt": "2022-04-20T14:30:00Z"},
    {"id": "550e8400-e29b-41d4-a716-446655440003", "label": "Windshield Repair", "createdAt": "2024-03-01T09:00:00Z"},
    {"id": "550e8400-e29b-41d4-a716-446655440004", "label": "Rust Treatment",    "createdAt": "2021-09-10T11:00:00Z"},
    {"id": "550e8400-e29b-41d4-a716-446655440005", "label": "Bodywork",          "createdAt": "2020-06-05T08:00:00Z"},
]

# Maps predefined category id → (display name, field keys) — mirrors app's PREDEFINED_WORK_ITEMS
PREDEFINED_ITEMS = {
    "oil-change":           ("Oil Change",             ["Brand", "Viscosity (e.g. 5W-30)"]),
    "tire-change-rotation": ("Tire Change / Rotation", []),
    "brake-service":        ("Brake Service",          ["Brand"]),
    "battery":              ("Battery",                ["Brand", "Capacity"]),
    "inspection":           ("Inspection",             []),
    "fluid-top-up":         ("Fluid Top-up",           ["Type"]),
    "filter-replacement":   ("Filter Replacement",     ["Brand", "Part Number"]),
}

# ---------------------------------------------------------------------------
# Vehicle definitions
# ---------------------------------------------------------------------------
VEHICLES = [
    {
        "id": "honda-civic",
        "nickname": "Honda Civic",
        "make": "Honda", "model": "Civic", "year": 2019,
        "vin": "2HGFC2F57KH589234", "licensePlate": "CIVI-419",
        "odometerUnit": "KM",
        "createdAt": "2019-03-01T14:00:00Z",
    },
    {
        "id": "ford-f-150",
        "nickname": "Ford F-150",
        "make": "Ford", "model": "F-150", "year": 2020,
        "vin": "1FTFW1E57LFA12345", "licensePlate": "F150-TRK",
        "odometerUnit": "MI",
        "createdAt": "2020-04-01T10:00:00Z",
    },
    {
        "id": "bmw-330i",
        "nickname": "BMW 330i",
        "make": "BMW", "model": "330i", "year": 2021,
        "vin": "WBA5R1C04MBA12345", "licensePlate": "BMW-330X",
        "odometerUnit": "KM",
        "createdAt": "2021-06-20T09:00:00Z",
    },
    {
        "id": "tesla-model-3",
        "nickname": "Tesla Model 3",
        "make": "Tesla", "model": "Model 3", "year": 2023,
        "vin": "5YJ3E1EA5PF123456", "licensePlate": "TES-M3",
        "odometerUnit": "KM",
        "createdAt": "2023-03-15T10:00:00Z",
    },
    {
        "id": "work-van",
        "nickname": "Work Van",
        "make": "Ford", "model": "Transit", "year": 2015,
        "vin": "1FTBW2CM5FKA12345", "licensePlate": "VAN-2015",
        "odometerUnit": "KM",
        "createdAt": "2015-05-01T08:00:00Z",
    },
    {
        # Minimal: no year / vin / licensePlate
        "id": "old-beater",
        "nickname": "Old Beater",
        "make": "Toyota", "model": "Corolla",
        "odometerUnit": "KM",
        "createdAt": "2020-05-01T09:00:00Z",
    },
    {
        "id": "new-honda-hr-v",
        "nickname": "New Honda HR-V",
        "make": "Honda", "model": "HR-V", "year": 2024,
        "vin": "3CZRZ2H57RM123456", "licensePlate": "HRV-2024",
        "odometerUnit": "KM",
        "createdAt": "2024-08-15T12:00:00Z",
    },
]

# ---------------------------------------------------------------------------
# Record definitions
# Each dict becomes a ServiceRecord JSON file.
# Fields performer / cost / currency / notes / attachments are optional.
# Add any extra keys to test ignoreUnknownKeys.
# Records listed under "attachments" will also get placeholder files created.
# ---------------------------------------------------------------------------
RECORDS = {

    # -----------------------------------------------------------------------
    # Honda Civic — 16 records, all 8 predefined categories + custom AC Service
    # -----------------------------------------------------------------------
    "honda-civic": [
        {
            "id": "a0c1v1c0-0000-4000-8000-000000000001",
            "date": "2019-03-22", "odometer": 2500,
            "category": "oil-change", "performer": "DIY",
            "cost": 45.99, "currency": "CAD",
            "notes": "First oil change. Castrol Edge 0W-20 full synthetic. Drain plug torqued to 30 Nm.",
            "workItems": [{"id": "oil-change", "name": "Oil Change", "fields": {"Brand": "Castrol Edge", "Viscosity (e.g. 5W-30)": "0W-20"}}],
            "createdAt": "2019-03-22T15:30:00Z",
        },
        {
            "id": "a0c1v1c0-0000-4000-8000-000000000002",
            "date": "2019-11-10", "odometer": 9800,
            "category": "tire-change-rotation", "performer": "Honda Downtown",
            "cost": 120.00, "currency": "CAD",
            "notes": "Winter tire swap. Stored summer set at dealership.",
            "createdAt": "2019-11-10T10:15:00Z",
        },
        {
            "id": "a0c1v1c0-0000-4000-8000-000000000003",
            "date": "2020-03-18", "odometer": 15200,
            "category": "inspection", "performer": "Honda Downtown",
            "cost": 89.99, "currency": "CAD",
            "notes": "Annual safety inspection passed. Minor comment on front wiper blades — replaced same day under warranty.",
            "createdAt": "2020-03-18T09:00:00Z",
        },
        {
            "id": "a0c1v1c0-0000-4000-8000-000000000004",
            "date": "2020-07-12", "odometer": 19500,
            "category": "filter-replacement", "performer": "DIY",
            "cost": 34.99, "currency": "CAD",
            "notes": "Replaced engine air filter and cabin air filter. Both quite dirty — earlier than expected from city driving.",
            "createdAt": "2020-07-12T11:00:00Z",
        },
        {
            "id": "a0c1v1c0-0000-4000-8000-000000000005",
            "date": "2020-11-07", "odometer": 24000,
            "category": "tire-change-rotation", "performer": "Honda Downtown",
            "cost": 120.00, "currency": "CAD",
            "createdAt": "2020-11-07T10:00:00Z",
        },
        {
            "id": "a0c1v1c0-0000-4000-8000-000000000006",
            "date": "2021-03-30", "odometer": 29500,
            "category": "oil-change", "performer": "Honda Downtown",
            "cost": 95.00, "currency": "CAD",
            "createdAt": "2021-03-30T13:00:00Z",
        },
        {
            # LONG NOTES
            "id": "a0c1v1c0-0000-4000-8000-000000000007",
            "date": "2021-08-22", "odometer": 34200,
            "category": "brake-service", "performer": "Honda Downtown",
            "cost": 349.00, "currency": "CAD",
            "notes": (
                "Front brake pads replaced and rotors resurfaced. Technician noted rear brakes "
                "still have approximately 60% pad life remaining and do not need attention yet. "
                "Brake fluid was also flushed as it had been 2 years since last flush — fluid "
                "had absorbed moisture and was showing signs of degradation. Recommended next "
                "brake inspection at 55,000 km or if any pulsation is noticed under braking."
            ),
            "createdAt": "2021-08-22T14:30:00Z",
        },
        {
            "id": "a0c1v1c0-0000-4000-8000-000000000008",
            "date": "2021-11-06", "odometer": 37500,
            "category": "tire-change-rotation", "performer": "Honda Downtown",
            "cost": 120.00, "currency": "CAD",
            "createdAt": "2021-11-06T10:00:00Z",
        },
        {
            # FULLY MINIMAL — no optional fields at all
            "id": "a0c1v1c0-0000-4000-8000-000000000009",
            "date": "2022-04-15", "odometer": 43000,
            "category": "oil-change",
            "createdAt": "2022-04-15T09:00:00Z",
        },
        {
            "id": "a0c1v1c0-0000-4000-8000-000000000010",
            "date": "2022-07-28", "odometer": 46500,
            "category": "fluid-top-up", "performer": "DIY",
            "cost": 22.50, "currency": "CAD",
            "notes": "Topped up windshield washer, brake fluid, and power steering reservoir.",
            "workItems": [{"id": "fluid-top-up", "name": "Fluid Top-up", "fields": {"Type": "Washer / Brake / Power Steering"}}],
            "createdAt": "2022-07-28T16:00:00Z",
        },
        {
            "id": "a0c1v1c0-0000-4000-8000-000000000011",
            "date": "2022-11-05", "odometer": 49800,
            "category": "other", "performer": "DIY",
            "cost": 67.00, "currency": "CAD",
            "notes": "Replaced serpentine belt (wear cracks) and lower radiator hose (small weep at clamp). Parts from RockAuto.",
            "createdAt": "2022-11-05T11:00:00Z",
        },
        {
            # HAS ATTACHMENTS — placeholder files will be created
            "id": "a0c1v1c0-0000-4000-8000-000000000012",
            "date": "2023-02-28", "odometer": 55200,
            "category": "oil-change", "performer": "Honda Downtown",
            "cost": 109.99, "currency": "CAD",
            "notes": "Full synthetic 0W-20. Fronts were 4 PSI low — corrected. Invoice and photo attached.",
            "attachments": ["invoice.pdf", "photo.jpg"],
            "createdAt": "2023-02-28T13:15:00Z",
        },
        {
            # SAME DATE as record 14
            "id": "a0c1v1c0-0000-4000-8000-000000000013",
            "date": "2023-11-04", "odometer": 66500,
            "category": "tire-change-rotation", "performer": "Honda Downtown",
            "cost": 130.00, "currency": "CAD",
            "notes": "Winter swap. New Michelin X-Ice Snow 205/55R16.",
            "createdAt": "2023-11-04T10:00:00Z",
        },
        {
            # SAME DATE as record 13 — two services in one visit
            "id": "a0c1v1c0-0000-4000-8000-000000000014",
            "date": "2023-11-04", "odometer": 66500,
            "category": "battery", "performer": "Honda Downtown",
            "cost": 219.99, "currency": "CAD",
            "notes": "Battery flagged during tire appointment — 68% capacity. Replaced proactively before winter. OEM-spec AGM 51R.",
            "workItems": [{"id": "battery", "name": "Battery", "fields": {"Brand": "OEM Honda", "Capacity": "AGM 51R"}}],
            "createdAt": "2023-11-04T10:45:00Z",
        },
        {
            # ZERO COST (warranty / prepaid plan)
            "id": "a0c1v1c0-0000-4000-8000-000000000015",
            "date": "2024-03-20", "odometer": 74000,
            "category": "oil-change", "performer": "Honda Downtown",
            "cost": 0.0, "currency": "CAD",
            "notes": "Covered under Honda Care prepaid maintenance plan. No out-of-pocket cost.",
            "createdAt": "2024-03-20T11:00:00Z",
        },
        {
            # CUSTOM CATEGORY + UNKNOWN EXTRA FIELD (tests ignoreUnknownKeys)
            "id": "a0c1v1c0-0000-4000-8000-000000000016",
            "date": "2024-07-15", "odometer": 80000,
            "category": "550e8400-e29b-41d4-a716-446655440001",
            "performer": "Honda Downtown",
            "cost": 279.99, "currency": "CAD",
            "notes": "AC recharge and leak check. Small leak at condenser O-ring — replaced as part of service.",
            "_futureField": "schema-v2-test-data",
            "createdAt": "2024-07-15T14:00:00Z",
        },
    ],

    # -----------------------------------------------------------------------
    # Ford F-150 — 8 records, MI / USD
    # -----------------------------------------------------------------------
    "ford-f-150": [
        {
            "id": "f150f150-0000-4000-8000-000000000001",
            "date": "2020-05-15", "odometer": 3200,
            "category": "oil-change", "performer": "Ford Country Dealer",
            "cost": 79.99, "currency": "USD",
            "notes": "First oil change. 5W-30 synthetic blend per owner's manual.",
            "workItems": [{"id": "oil-change", "name": "Oil Change", "fields": {"Brand": "", "Viscosity (e.g. 5W-30)": "5W-30"}}],
            "createdAt": "2020-05-15T09:30:00Z",
        },
        {
            "id": "f150f150-0000-4000-8000-000000000002",
            "date": "2020-10-22", "odometer": 8500,
            "category": "tire-change-rotation", "performer": "Ford Country Dealer",
            "cost": 199.99, "currency": "USD",
            "createdAt": "2020-10-22T10:00:00Z",
        },
        {
            "id": "f150f150-0000-4000-8000-000000000003",
            "date": "2021-05-10", "odometer": 14000,
            "category": "oil-change", "performer": "Ford Country Dealer",
            "cost": 84.99, "currency": "USD",
            "createdAt": "2021-05-10T11:00:00Z",
        },
        {
            "id": "f150f150-0000-4000-8000-000000000004",
            "date": "2021-09-20", "odometer": 17500,
            "category": "battery", "performer": "Ford Country Dealer",
            "cost": 189.99, "currency": "USD",
            "notes": "Battery failed load test — 40% capacity. Replaced with Motorcraft BXT-65-750 AGM before winter.",
            "workItems": [{"id": "battery", "name": "Battery", "fields": {"Brand": "Motorcraft BXT-65-750", "Capacity": "AGM"}}],
            "createdAt": "2021-09-20T14:00:00Z",
        },
        {
            "id": "f150f150-0000-4000-8000-000000000005",
            "date": "2022-05-05", "odometer": 25000,
            "category": "oil-change", "performer": "Quick Lube Express",
            "cost": 74.99, "currency": "USD",
            "createdAt": "2022-05-05T09:00:00Z",
        },
        {
            "id": "f150f150-0000-4000-8000-000000000006",
            "date": "2022-09-18", "odometer": 28000,
            "category": "inspection", "performer": "Ford Country Dealer",
            "cost": 99.00, "currency": "USD",
            "notes": "Annual state inspection passed. Tread: F 7/32, R 8/32. Brakes: F 6mm, R 7mm.",
            "createdAt": "2022-09-18T10:30:00Z",
        },
        {
            "id": "f150f150-0000-4000-8000-000000000007",
            "date": "2023-04-12", "odometer": 35500,
            "category": "oil-change", "performer": "Quick Lube Express",
            "cost": 89.99, "currency": "USD",
            "createdAt": "2023-04-12T08:45:00Z",
        },
        {
            # LONG NOTES
            "id": "f150f150-0000-4000-8000-000000000008",
            "date": "2023-10-08", "odometer": 41000,
            "category": "brake-service", "performer": "Ford Country Dealer",
            "cost": 449.99, "currency": "USD",
            "notes": (
                "Full front brake job: pads and rotors replaced. Rear brakes at 5mm — borderline "
                "but acceptable until next service. Brake fluid moisture above 3%, so full flush "
                "included. Caliper slides cleaned and lubed. Next: rear brakes by 55,000 miles."
            ),
            "createdAt": "2023-10-08T13:00:00Z",
        },
    ],

    # -----------------------------------------------------------------------
    # BMW 330i — 6 records, EUR
    # -----------------------------------------------------------------------
    "bmw-330i": [
        {
            "id": "b330b330-0000-4000-8000-000000000001",
            "date": "2021-07-05", "odometer": 3000,
            "category": "oil-change", "performer": "BMW Autohaus",
            "cost": 250.00, "currency": "EUR",
            "notes": "First oil change. BMW LL-01 5W-30 full synthetic. Oil filter housing gasket inspected — no seepage.",
            "workItems": [{"id": "oil-change", "name": "Oil Change", "fields": {"Brand": "BMW LL-01", "Viscosity (e.g. 5W-30)": "5W-30"}}],
            "createdAt": "2021-07-05T10:30:00Z",
        },
        {
            "id": "b330b330-0000-4000-8000-000000000002",
            "date": "2022-01-20", "odometer": 18000,
            "category": "inspection", "performer": "BMW Autohaus",
            "cost": 450.00, "currency": "EUR",
            "notes": "BMW Inspection I. Engine mounts slight softness — within tolerance. Microfilter replaced. Brake wear sensors front/rear at green.",
            "createdAt": "2022-01-20T09:00:00Z",
        },
        {
            "id": "b330b330-0000-4000-8000-000000000003",
            "date": "2022-07-25", "odometer": 27000,
            "category": "tire-change-rotation", "performer": "BMW Autohaus",
            "cost": 180.00, "currency": "EUR",
            "createdAt": "2022-07-25T11:00:00Z",
        },
        {
            "id": "b330b330-0000-4000-8000-000000000004",
            "date": "2023-01-18", "odometer": 36000,
            "category": "oil-change", "performer": "BMW Autohaus",
            "cost": 280.00, "currency": "EUR",
            "createdAt": "2023-01-18T10:00:00Z",
        },
        {
            # HAS ATTACHMENT
            "id": "b330b330-0000-4000-8000-000000000005",
            "date": "2023-07-10", "odometer": 45000,
            "category": "brake-service", "performer": "BMW Autohaus",
            "cost": 649.00, "currency": "EUR",
            "notes": "Front and rear pads replaced. Front rotors replaced (excessive scoring). Brake fluid flush. Invoice attached.",
            "attachments": ["invoice.pdf"],
            "createdAt": "2023-07-10T14:00:00Z",
        },
        {
            "id": "b330b330-0000-4000-8000-000000000006",
            "date": "2024-01-22", "odometer": 54000,
            "category": "inspection", "performer": "BMW Autohaus",
            "cost": 490.00, "currency": "EUR",
            "notes": "BMW Inspection II. Spark plugs replaced. Transmission fluid changed. DSC software updated via dealer tool.",
            "createdAt": "2024-01-22T11:00:00Z",
        },
    ],

    # -----------------------------------------------------------------------
    # Tesla Model 3 — 4 records, EV (no oil-change), custom Windshield Repair
    # -----------------------------------------------------------------------
    "tesla-model-3": [
        {
            "id": "te5la300-0000-4000-8000-000000000001",
            "date": "2023-12-10", "odometer": 15000,
            "category": "inspection", "performer": "Tesla Service Centre",
            "cost": 99.00, "currency": "CAD",
            "notes": "Annual multi-point inspection. Cabin air filter replaced. HV battery health: 97%.",
            "createdAt": "2023-12-10T10:00:00Z",
        },
        {
            "id": "te5la300-0000-4000-8000-000000000002",
            "date": "2024-04-20", "odometer": 22000,
            "category": "tire-change-rotation", "performer": "Kal Tire",
            "cost": 200.00, "currency": "CAD",
            "notes": "Rotation and balance. Tread: FL 8/32, FR 8/32, RL 7/32, RR 7/32.",
            "createdAt": "2024-04-20T11:00:00Z",
        },
        {
            # CUSTOM CATEGORY — Windshield Repair
            "id": "te5la300-0000-4000-8000-000000000003",
            "date": "2024-06-05", "odometer": 26500,
            "category": "550e8400-e29b-41d4-a716-446655440003",
            "performer": "AutoGlass Now",
            "cost": 149.00, "currency": "CAD",
            "notes": "5mm chip repair from highway rock strike. Resin injection — no optical distortion remaining.",
            "createdAt": "2024-06-05T09:30:00Z",
        },
        {
            "id": "te5la300-0000-4000-8000-000000000004",
            "date": "2024-11-02", "odometer": 32000,
            "category": "tire-change-rotation", "performer": "Kal Tire",
            "cost": 200.00, "currency": "CAD",
            "createdAt": "2024-11-02T10:30:00Z",
        },
    ],

    # -----------------------------------------------------------------------
    # Work Van — 18 records, 2015-2024, high odometer (~304 000 km)
    # -----------------------------------------------------------------------
    "work-van": [
        {"id": "w0rkvan0-0000-4000-8000-000000000001", "date": "2015-05-20", "odometer": 5000,   "category": "oil-change",           "performer": "Ford Country Dealer", "cost": 85.00,  "currency": "CAD", "notes": "First oil change at delivery km.",                                                                                                                             "createdAt": "2015-05-20T10:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000002", "date": "2016-01-15", "odometer": 25000,  "category": "oil-change",           "performer": "Jiffy Lube",          "cost": 79.99,  "currency": "CAD",                                                                                                                                                                     "createdAt": "2016-01-15T09:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000003", "date": "2016-09-22", "odometer": 45000,  "category": "brake-service",        "performer": "Midas",               "cost": 320.00, "currency": "CAD", "notes": "Front pads and rotors. Rear shoes inspected — still serviceable.",                                                                                          "createdAt": "2016-09-22T11:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000004", "date": "2017-04-10", "odometer": 65000,  "category": "tire-change-rotation", "performer": "Kal Tire",            "cost": 150.00, "currency": "CAD",                                                                                                                                                                     "createdAt": "2017-04-10T10:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000005", "date": "2017-10-18", "odometer": 85000,  "category": "oil-change",           "performer": "Jiffy Lube",          "cost": 79.99,  "currency": "CAD",                                                                                                                                                                     "createdAt": "2017-10-18T09:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000006", "date": "2018-03-25", "odometer": 105000, "category": "filter-replacement",   "performer": "DIY",                 "cost": 55.00,  "currency": "CAD", "notes": "Engine air filter and fuel filter. No cabin filter on this trim.",                                                                                          "createdAt": "2018-03-25T11:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000007", "date": "2018-09-12", "odometer": 125000, "category": "oil-change",           "performer": "Jiffy Lube",          "cost": 84.99,  "currency": "CAD",                                                                                                                                                                     "createdAt": "2018-09-12T09:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000008", "date": "2019-03-20", "odometer": 145000, "category": "brake-service",        "performer": "Midas",               "cost": 380.00, "currency": "CAD", "notes": "Front pads, rotors, and calipers. Left front caliper was seizing. Rear drums also replaced.",                                                               "createdAt": "2019-03-20T10:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000009", "date": "2019-09-25", "odometer": 165000, "category": "oil-change",           "performer": "Jiffy Lube",          "cost": 84.99,  "currency": "CAD",                                                                                                                                                                     "createdAt": "2019-09-25T09:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000010", "date": "2020-04-12", "odometer": 185000, "category": "tire-change-rotation", "performer": "Kal Tire",            "cost": 160.00, "currency": "CAD", "notes": "New all-seasons — previous set at wear indicators. Goodyear Cargo G26 load range C.",                                                                       "createdAt": "2020-04-12T10:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000011", "date": "2020-10-22", "odometer": 205000, "category": "oil-change",           "performer": "Jiffy Lube",          "cost": 89.99,  "currency": "CAD",                                                                                                                                                                     "createdAt": "2020-10-22T09:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000012", "date": "2021-04-15", "odometer": 220000, "category": "inspection",           "performer": "City Fleet Services", "cost": 120.00, "currency": "CAD", "notes": "Annual commercial safety inspection. Passed. Exhaust clamp worn — monitored.",                                                                             "createdAt": "2021-04-15T11:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000013", "date": "2021-10-20", "odometer": 237000, "category": "oil-change",           "performer": "Jiffy Lube",          "cost": 89.99,  "currency": "CAD",                                                                                                                                                                     "createdAt": "2021-10-20T09:00:00Z"},
        {
            # LONG NOTES — breakdowns story
            "id": "w0rkvan0-0000-4000-8000-000000000014",
            "date": "2022-04-18", "odometer": 252000,
            "category": "battery", "performer": "Roadside Assist / Canadian Tire",
            "workItems": [{"id": "battery", "name": "Battery", "fields": {"Brand": "Group 65 Heavy-Duty", "Capacity": ""}}],
            "cost": 245.00, "currency": "CAD",
            "notes": (
                "Battery died mid-delivery on Highway 401 — left lane, morning rush hour. "
                "Called roadside, two-hour wait. Battery was original OEM unit (7 years old). "
                "Canadian Tire installed a Group 65 heavy-duty replacement on the spot. "
                "Brief charging warning after restart — cleared after a full drive cycle. "
                "Alternator output confirmed normal at 14.2V. Costly day: lost two delivery "
                "runs; roadside fee was $95 on top of the battery."
            ),
            "createdAt": "2022-04-18T14:00:00Z",
        },
        {"id": "w0rkvan0-0000-4000-8000-000000000015", "date": "2022-10-15", "odometer": 265000, "category": "oil-change",           "performer": "Jiffy Lube",          "cost": 94.99,  "currency": "CAD",                                                                                                                                                                     "createdAt": "2022-10-15T09:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000016", "date": "2023-04-20", "odometer": 278000, "category": "tire-change-rotation", "performer": "Kal Tire",            "cost": 160.00, "currency": "CAD", "notes": "Full replacement — all four at wear indicator. Goodyear Endurance ST load range E.",                                                                        "createdAt": "2023-04-20T10:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000017", "date": "2023-10-18", "odometer": 291000, "category": "oil-change",           "performer": "Jiffy Lube",          "cost": 94.99,  "currency": "CAD",                                                                                                                                                                     "createdAt": "2023-10-18T09:00:00Z"},
        {"id": "w0rkvan0-0000-4000-8000-000000000018", "date": "2024-04-15", "odometer": 304000, "category": "brake-service",        "performer": "Midas",               "cost": 520.00, "currency": "CAD", "notes": "Front brakes worn to metal. Both front calipers seizing. Replaced pads, rotors, and calipers. Rear brake lines flagged for next visit (surface rust).",  "createdAt": "2024-04-15T14:00:00Z"},
    ],

    # -----------------------------------------------------------------------
    # Old Beater — 3 records; vehicle has no year / VIN / plate
    # -----------------------------------------------------------------------
    "old-beater": [
        {
            # FULLY MINIMAL — only the required fields; empty workItems tests backward-compat display
            "id": "0ldb34t0-0000-4000-8000-000000000001",
            "date": "2020-05-10", "odometer": 185000,
            "category": "oil-change",
            "workItems": [],
            "createdAt": "2020-05-10T10:00:00Z",
        },
        {
            # CUSTOM CATEGORY — Rust Treatment
            "id": "0ldb34t0-0000-4000-8000-000000000002",
            "date": "2021-06-20", "odometer": 195000,
            "category": "550e8400-e29b-41d4-a716-446655440004",
            "performer": "RustCheck",
            "cost": 130.00, "currency": "CAD",
            "notes": "Annual rust inhibitor. Underbody, wheel wells, door sills treated. Bad surface rust on rear subframe — treated, flagged for monitoring.",
            "createdAt": "2021-06-20T11:00:00Z",
        },
        {
            # VERY LONG NOTES — marathon DIY session; multiple custom workItems
            "id": "0ldb34t0-0000-4000-8000-000000000003",
            "date": "2022-08-05", "odometer": 205000,
            "category": "other", "performer": "DIY",
            "cost": 210.00, "currency": "CAD",
            "workItems": [
                {"id": "custom-intake-gasket", "name": "Intake Manifold Gasket", "fields": {}},
                {"id": "custom-thermostat",    "name": "Thermostat & Housing",   "fields": {}},
                {"id": "custom-cv-axle",       "name": "CV Axle (Front Passenger)", "fields": {}},
                {"id": "custom-spark-plugs",   "name": "Spark Plugs", "fields": {"Brand": "NGK Iridium"}},
            ],
            "notes": (
                "Marathon DIY day — multiple items addressed at once. "
                "(1) Replaced cracked intake manifold gasket causing rough idle and P0171 lean code. "
                "(2) Replaced thermostat and housing — coolant temp gauge reading erratic. "
                "(3) Replaced front passenger CV axle — clicking on turns. "
                "(4) Topped up power steering fluid; small weep at rack boot — not leaking yet. "
                "(5) Replaced all four spark plugs with NGK iridium — originals, likely never changed. "
                "Parts from RockAuto: $187. Total time: ~9 hours."
            ),
            "createdAt": "2022-08-05T18:00:00Z",
        },
    ],

    # -----------------------------------------------------------------------
    # New Honda HR-V — intentionally empty (zero-records UI state)
    # -----------------------------------------------------------------------
    "new-honda-hr-v": [],
}


# ---------------------------------------------------------------------------
# Build a record dict conforming to ServiceRecord schema
# ---------------------------------------------------------------------------
def make_record(vehicle_id: str, r: dict) -> dict:
    record = {
        "schemaVersion": 1,
        "id":         r["id"],
        "vehicleId":  vehicle_id,
        "date":       r["date"],
        "odometer":   r["odometer"],
        "category":   r["category"],
    }
    # Optional fields — only include if present in source data
    for key in ("performer", "cost", "currency", "notes"):
        if key in r:
            record[key] = r[key]
    record["attachments"] = r.get("attachments", [])
    # Preserve any extra / unknown fields (e.g. _futureField)
    for key, val in r.items():
        if key not in record and key not in ("id", "date", "odometer", "category", "createdAt", "workItems"):
            record[key] = val
    # workItems: use explicit list if provided, else auto-derive from predefined category
    if "workItems" in r:
        record["workItems"] = r["workItems"]
    elif r["category"] in PREDEFINED_ITEMS:
        name, _ = PREDEFINED_ITEMS[r["category"]]
        record["workItems"] = [{"id": r["category"], "name": name, "fields": {}}]
    else:
        record["workItems"] = []
    record["createdAt"] = r["createdAt"]
    record["updatedAt"] = r.get("updatedAt", r["createdAt"])
    return record


# ---------------------------------------------------------------------------
# Main generation logic
# ---------------------------------------------------------------------------
def generate() -> None:
    # categories.json
    write(
        os.path.join(OUT, "categories.json"),
        {"schemaVersion": 1, "custom": CUSTOM_CATEGORIES},
    )

    for v in VEHICLES:
        vehicle_id = v["id"]
        vehicle_dir = os.path.join(OUT, "vehicles", vehicle_id)
        records_dir = os.path.join(vehicle_dir, "records")

        # Compute cached metadata from records
        vehicle_records = RECORDS.get(vehicle_id, [])
        last_service_date = max((r["date"] for r in vehicle_records), default=None)
        record_count = len(vehicle_records)

        # vehicle.json (includes cached lastServiceDate and recordCount)
        write(
            os.path.join(vehicle_dir, "vehicle.json"),
            {
                "schemaVersion": 1,
                **v,
                "lastServiceDate": last_service_date,
                "recordCount": record_count,
            },
        )

        # Ensure records dir exists (important for zero-record vehicle)
        os.makedirs(records_dir, exist_ok=True)

        for r in vehicle_records:
            record = make_record(vehicle_id, r)
            write(os.path.join(records_dir, f"{r['id']}.json"), record)

            # Create attachment placeholder files
            for filename in r.get("attachments", []):
                placeholder = os.path.join(records_dir, r["id"], filename)
                write(placeholder, "placeholder\n")

    # Create zip archive: sample-vault/Yorvana.zip
    archive_path = os.path.join(SCRIPT_DIR, "Yorvana.zip")
    with zipfile.ZipFile(archive_path, "w", zipfile.ZIP_DEFLATED) as zf:
        for dirpath, _, filenames in os.walk(OUT):
            for filename in filenames:
                abs_path = os.path.join(dirpath, filename)
                arcname = os.path.relpath(abs_path, SCRIPT_DIR)
                zf.write(abs_path, arcname)

    print(f"Vault written to: {OUT}")
    total_records = sum(len(v) for v in RECORDS.values())
    print(f"  {len(VEHICLES)} vehicles, {total_records} records")
    print(f"Archive: {archive_path}")


if __name__ == "__main__":
    generate()
