<script>
    import { onMount, onDestroy } from 'svelte';
    import { VehiclePartRenderer } from '../../core/rendering/VehiclePartRenderer.js';
    import { WdbParser, findRoi, buildPartsMap } from '../../core/formats/WdbParser.js';
    import {
        LegoColorNames,
        VehicleWorlds,
        VehicleModels,
        VehicleNames,
        VehiclePartColors,
        TexturedParts,
        Act1PlaneIndices
    } from '../../core/savegame/constants.js';
    import { squareTexture } from '../../core/savegame/imageQuantizer.js';
    import { parseTex } from '../../core/formats/TexParser.js';
    import { fetchTexture } from '../../core/assetLoader.js';
    import NavButton from '../NavButton.svelte';
    import ResetButton from '../ResetButton.svelte';
    import EditorTooltip from '../EditorTooltip.svelte';
    import TexturePickerModal from './TexturePickerModal.svelte';
    import './editor-common.css';

    export let slot;
    export let onUpdate = () => {};

    // Build flat list of all parts across all vehicles
    const vehicleList = ['dunebuggy', 'helicopter', 'jetski', 'racecar'];
    const allParts = vehicleList.flatMap(v =>
        (VehiclePartColors[v] || []).map(p => ({ ...p, vehicle: v }))
    );

    let canvas;
    let renderer = null;
    let loading = true;
    let error = null;
    let partError = null;

    // Cached WDB data
    let wdbParser = null;
    let wdbData = null;

    let globalIndex = 0;

    // Track current loaded part to avoid redundant reloads
    let loadedPartKey = null;

    // Texture modal state
    let showTextureModal = false;
    let texturePalette = null;
    let wdbTexture = null;
    let preloadedDefaults = null;

    // Current part info from flat list
    $: currentEntry = allParts[globalIndex];
    $: vehicle = currentEntry?.vehicle || 'dunebuggy';
    $: currentPart = currentEntry;

    // Get current color from slot variables
    $: currentColorValue = currentPart
        ? slot?.variables?.get(currentPart.variable)?.value || currentPart.defaultColor
        : 'lego red';

    // Check if current color differs from default
    $: isDefaultColor = currentPart && currentColorValue === currentPart.defaultColor;

    // Texture info for current part (if it's a textured part)
    $: textureInfo = currentPart ? TexturedParts[currentPart.part] || null : null;

    // Check if vehicle has a plane in Act1State (vehicle is placed in world)
    $: vehicleHasPlane = (() => {
        if (!textureInfo || !slot?.act1State) return false;
        const planeIdx = Act1PlaneIndices[textureInfo.vehicle];
        return planeIdx !== undefined && slot.act1State.planes[planeIdx]?.nameLength > 0;
    })();

    // Can edit texture: part has texture info AND vehicle plane exists in Act1State
    $: canEditTexture = textureInfo && vehicleHasPlane;

    onMount(async () => {
        try {
            // Load and parse WDB once
            const response = await fetch('/LEGO/data/WORLD.WDB');
            if (!response.ok) {
                throw new Error(`Failed to load WORLD.WDB: ${response.status}`);
            }

            const buffer = await response.arrayBuffer();
            wdbParser = new WdbParser(buffer);
            wdbData = wdbParser.parse();

            // Initialize renderer
            renderer = new VehiclePartRenderer(canvas);

            // Load initial part
            await loadCurrentPart();

            renderer.start();
            loading = false;
        } catch (e) {
            console.error('VehiclePartColorEditor initialization error:', e);
            error = e.message;
            loading = false;
        }
    });

    onDestroy(() => {
        renderer?.dispose();
    });

    // Reload part when index or slot changes
    $: if (renderer && !loading && currentPart) {
        const partKey = `${slot?.slotNumber}-${vehicle}-${globalIndex}`;
        if (partKey !== loadedPartKey) {
            loadCurrentPart();
        }
    }

    // Check if current texture matches the WDB default.
    // Declared after the loadCurrentPart block: Svelte 5 runs legacy $: effects
    // in source order, and wdbTexture must be set before this evaluates.
    function isTextureDefault(info, wdbTex, act1Textures) {
        if (!info || !wdbTex || !act1Textures) return true;
        const act1Tex = act1Textures.get(info.textureName.toLowerCase());
        if (!act1Tex) return true;
        if (act1Tex.pixels.length !== wdbTex.pixels.length) return false;
        for (let i = 0; i < act1Tex.pixels.length; i++) {
            if (act1Tex.pixels[i] !== wdbTex.pixels[i]) return false;
        }
        return true;
    }

    $: isDefaultTexture = isTextureDefault(textureInfo, wdbTexture, slot?.act1State?.textures);

    // Update color when variable changes (without reloading geometry)
    $: if (renderer && !loading && currentColorValue && loadedPartKey) {
        renderer.updateColor(currentColorValue);
    }

    async function loadCurrentPart() {
        if (!wdbData || !wdbParser || !currentPart || !renderer) return;

        partError = null;
        const partKey = `${slot?.slotNumber}-${vehicle}-${globalIndex}`;

        try {
            const worldName = VehicleWorlds[vehicle];
            const modelName = VehicleModels[vehicle];

            // Find the vehicle world
            const world = wdbData.worlds.find(w => w.name === worldName);
            if (!world) {
                partError = `World ${worldName} not found`;
                return;
            }

            // Find the vehicle model
            const model = world.models.find(m =>
                m.name.toLowerCase() === modelName.toLowerCase()
            );
            if (!model) {
                partError = `Model ${modelName} not found`;
                return;
            }

            // Parse model data
            const modelData = wdbParser.parseModelData(model.dataOffset);

            // Find the part ROI
            const partRoi = findRoi(modelData.roi, currentPart.part);
            if (!partRoi) {
                partError = `Part not found`;
                return;
            }

            // Build parts map for shared LOD resolution
            const partsMap = buildPartsMap(wdbParser, world.parts);

            // Build texture list, merging Act1State texture if available
            let textures = modelData.textures || [];
            if (textureInfo && slot?.act1State?.textures) {
                const texKey = textureInfo.textureName.toLowerCase();
                const act1Tex = slot.act1State.textures.get(texKey);
                if (act1Tex) {
                    const existingIdx = textures.findIndex(t => t.name?.toLowerCase() === texKey);
                    if (existingIdx >= 0) {
                        textures = [...textures];
                        textures[existingIdx] = { ...act1Tex, name: texKey };
                    } else {
                        textures = [...textures, { ...act1Tex, name: texKey }];
                    }
                }
            }

            // Extract palette from the WDB texture (the ground truth) for the
            // texture picker modal. The game's LoadBits() only overwrites pixel
            // data on the DirectDraw surface — the palette always stays from the
            // original WDB load. So custom pixel indices must reference THIS palette.
            if (textureInfo) {
                const texKey = textureInfo.textureName.toLowerCase();
                const wdbTex = (modelData.textures || []).find(t => t.name === texKey);
                if (wdbTex) {
                    texturePalette = wdbTex.palette;
                    wdbTexture = squareTexture(wdbTex);
                } else {
                    wdbTexture = null;
                }
            } else {
                wdbTexture = null;
            }

            // Load part with current color, textures, and parts map for shared LOD lookup
            renderer.loadPartWithColor(partRoi, currentColorValue, textures, partsMap)
            loadedPartKey = partKey;

            // Preload default .tex files in background for the texture picker
            if (textureInfo) {
                preloadDefaultTextures(textureInfo);
            } else {
                preloadedDefaults = null;
            }
        } catch (e) {
            console.error('Failed to load part:', e);
            partError = e.message;
        }
    }

    async function preloadDefaultTextures(info) {
        const results = await Promise.all(info.texFiles.map(async (texFile) => {
            const buffer = await fetchTexture(texFile);
            if (!buffer) return null;
            const parsed = parseTex(buffer);
            if (parsed.textures.length > 0) {
                return { name: texFile, ...parsed.textures[0] };
            }
            return null;
        }));
        // Only apply if textureInfo hasn't changed since we started
        if (textureInfo === info) {
            preloadedDefaults = results.filter(Boolean);
        }
    }

    function prevPart() {
        globalIndex = globalIndex > 0 ? globalIndex - 1 : allParts.length - 1;
        loadedPartKey = null;
    }

    function nextPart() {
        globalIndex = globalIndex < allParts.length - 1 ? globalIndex + 1 : 0;
        loadedPartKey = null;
    }

    function cycleColor() {
        if (!currentPart || partError) return;
        if (renderer?.wasDragged()) return;

        // Find current color index and cycle to next
        const currentIdx = LegoColorNames.indexOf(currentColorValue);
        const nextIdx = (currentIdx + 1) % LegoColorNames.length;
        const nextColor = LegoColorNames[nextIdx];

        onUpdate({
            variable: {
                name: currentPart.variable,
                value: nextColor
            }
        });
    }

    function resetColor() {
        if (!currentPart) return;

        const update = {
            variable: {
                name: currentPart.variable,
                value: currentPart.defaultColor
            }
        };

        // Reset texture to WDB default (equivalent to WriteDefaultTexture in the game).
        // wdbTexture is already squared when cached in loadCurrentPart().
        if (canEditTexture && wdbTexture && renderer) {
            const texKey = textureInfo.textureName.toLowerCase();

            renderer.updateTexture(texKey, wdbTexture);

            update.texture = {
                textureName: textureInfo.textureName,
                textureData: wdbTexture
            };
        }

        onUpdate(update);
    }

    function openTexturePicker() {
        if (!canEditTexture) return;
        showTextureModal = true;
    }

    function handleTextureSelect(textureData) {
        if (!textureInfo || !renderer) return;

        const texKey = textureInfo.textureName.toLowerCase();

        // Square the texture for game compatibility — the game's DirectDraw
        // surfaces are always square, and LoadBits() expects matching dimensions.
        // No-op if already square.
        const saveData = squareTexture(textureData);

        // Update preview immediately
        renderer.updateTexture(texKey, saveData);

        // Save to file
        onUpdate({
            texture: {
                textureName: textureInfo.textureName,
                textureData: saveData
            }
        });

        showTextureModal = false;
    }

</script>

<EditorTooltip text="Click on the part to cycle through colors. Use the texture button to customize textures on supported parts (vehicle must be fully built first). Changes are automatically saved." onResetCamera={() => renderer?.resetView()}>
    <!-- 3D Preview (clickable to cycle color) -->
    <div class="preview-container">
        <canvas
            bind:this={canvas}
            width="190"
            height="190"
            class:hidden={loading || error}
            onclick={cycleColor}
            role="button"
            tabindex="0"
            aria-label="Click to change color"
        ></canvas>

        {#if loading}
            <div class="preview-overlay">
                <div class="spinner"></div>
            </div>
        {:else if error}
            <div class="preview-overlay error">{error}</div>
        {:else if partError}
            <div class="preview-overlay error">{partError}</div>
        {/if}
    </div>

    <!-- Part navigation below canvas -->
    <div class="part-nav-wrapper">
        <div class="part-nav">
            <NavButton direction="left" onclick={prevPart} />
            <div class="part-info">
                <span class="nav-index">{VehicleNames[vehicle]}</span>
                <span class="nav-name">{currentPart?.label || 'Unknown'}</span>
            </div>
            <NavButton direction="right" onclick={nextPart} />
        </div>
        {#if textureInfo}
            <button
                type="button"
                class="side-btn"
                class:disabled={!canEditTexture}
                onclick={openTexturePicker}
                disabled={!canEditTexture}
                title={canEditTexture ? 'Edit texture' : 'Vehicle not placed in world'}
            >
                <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
                    <path d="M14.5 2.5a1.5 1.5 0 0 0-3 0v1h-2v-1a1.5 1.5 0 0 0-3 0v1h-2v-1a1.5 1.5 0 0 0-3 0v2h1v2h-1v2h1v2h-1v2a1.5 1.5 0 0 0 3 0v-1h2v1a1.5 1.5 0 0 0 3 0v-1h2v1a1.5 1.5 0 0 0 3 0v-2h-1v-2h1v-2h-1v-2h1v-2zm-3 2h-2v2h2v-2zm-5 0h-2v2h2v-2zm0 4h-2v2h2v-2zm5 0h-2v2h2v-2z"/>
                </svg>
            </button>
        {/if}
    </div>

    <div class="reset-container">
        {#if (!isDefaultColor || !isDefaultTexture) && !loading && !error && !partError}
            <ResetButton onclick={resetColor} />
        {/if}
    </div>
</EditorTooltip>

{#if showTextureModal && textureInfo}
    <TexturePickerModal
        {textureInfo}
        palette={texturePalette}
        defaults={preloadedDefaults}
        onSelect={handleTextureSelect}
        onClose={() => showTextureModal = false}
    />
{/if}

