'use strict';

var SceneImporter = require('./SceneImporter.js');

/**
 * @file SpineImporter.ts
 * @description Spine 2D Skeletal Animation Importer
 * PRO-1.3: Scene Format Interoperability
 *
 * Spine is a professional 2D skeletal animation tool.
 * Supports both JSON and binary formats.
 *
 * File Types:
 * - .json - Spine skeleton data (JSON)
 * - .skel - Spine skeleton data (binary)
 * - .atlas - Texture atlas
 */
// ============================================================
// Spine Importer
// ============================================================
class SpineImporter extends SceneImporter.SceneImporter {
    constructor() {
        super('spine');
        this.atlas = null;
    }
    canHandle(filePath) {
        const ext = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();
        return ext === '.json' || ext === '.skel' || ext === '.atlas';
    }
    async import(options) {
        const startTime = Date.now();
        this.options = options;
        this.clearMessages();
        const scenes = [];
        const assets = [];
        try {
            const ext = options.sourcePath.substring(options.sourcePath.lastIndexOf('.')).toLowerCase();
            if (ext === '.atlas') {
                // Parse atlas file
                const atlasContent = await this.readFile(options.sourcePath);
                this.atlas = this.parseAtlas(atlasContent);
                // Create assets from atlas
                for (const page of this.atlas.pages) {
                    assets.push({
                        id: this.generateId(),
                        name: page.name,
                        type: 'texture',
                        path: page.name,
                        data: {
                            width: page.size.width,
                            height: page.size.height,
                            format: page.format,
                            filter: page.filter,
                            pma: page.pma,
                        },
                    });
                    // Add individual regions as sprite assets
                    for (const region of page.regions) {
                        assets.push({
                            id: this.generateId(),
                            name: region.name,
                            type: 'texture',
                            path: page.name,
                            data: {
                                frame: region.bounds,
                                rotate: region.rotate,
                                index: region.index,
                            },
                        });
                    }
                }
            }
            else {
                // Parse skeleton file
                const content = await this.readFile(options.sourcePath);
                const skeleton = JSON.parse(content);
                // Try to load atlas if resolveDependencies is enabled
                if (options.resolveDependencies) {
                    const atlasPath = options.sourcePath.replace(/\.(json|skel)$/i, '.atlas');
                    try {
                        const atlasContent = await this.readFile(atlasPath);
                        this.atlas = this.parseAtlas(atlasContent);
                    }
                    catch (_a) {
                        this.addWarning('ATLAS_NOT_FOUND', 'Atlas file not found', atlasPath);
                    }
                }
                // Convert skeleton to scene
                const scene = this.convertSkeletonToScene(skeleton, options.sourcePath);
                scenes.push(scene);
                // Extract assets from skins
                for (const skin of skeleton.skins) {
                    for (const [slotName, attachments] of Object.entries(skin.attachments)) {
                        for (const [attachmentName, attachment] of Object.entries(attachments)) {
                            if ('width' in attachment && 'height' in attachment) {
                                const regionAttachment = attachment;
                                assets.push({
                                    id: this.generateId(),
                                    name: regionAttachment.path || attachmentName,
                                    type: 'texture',
                                    path: regionAttachment.path || attachmentName,
                                    data: {
                                        width: regionAttachment.width,
                                        height: regionAttachment.height,
                                        slot: slotName,
                                        skin: skin.name,
                                    },
                                });
                            }
                        }
                    }
                }
            }
        }
        catch (error) {
            this.addError('IMPORT_ERROR', `Failed to import: ${error}`, options.sourcePath, undefined, true);
        }
        return this.createResult(scenes, assets, startTime);
    }
    async parseFile(content, filePath) {
        const stringContent = typeof content === 'string' ? content : new TextDecoder().decode(content);
        try {
            if (filePath.endsWith('.atlas')) {
                return null; // Atlas files don't produce scenes
            }
            const skeleton = JSON.parse(stringContent);
            return this.convertSkeletonToParsed(skeleton);
        }
        catch (_a) {
            return null;
        }
    }
    async validate(sourcePath) {
        const errors = [];
        if (!this.canHandle(sourcePath)) {
            errors.push({
                code: 'INVALID_FORMAT',
                message: `File ${sourcePath} is not a valid Spine file`,
                file: sourcePath,
                fatal: true,
            });
        }
        return errors;
    }
    // ----------------------------------------
    // Skeleton Conversion
    // ----------------------------------------
    convertSkeletonToScene(skeleton, sourcePath) {
        var _a, _b, _c;
        const name = this.extractFileName(sourcePath);
        const entities = [];
        // Create root entity
        const rootEntity = {
            id: this.generateId(),
            name: name,
            components: [
                {
                    type: 'Transform',
                    data: {
                        position: { x: skeleton.skeleton.x || 0, y: skeleton.skeleton.y || 0 },
                        rotation: 0,
                        scale: { x: 1, y: 1 },
                    },
                },
                {
                    type: 'SpineSkeleton',
                    data: {
                        version: skeleton.skeleton.spine,
                        width: skeleton.skeleton.width,
                        height: skeleton.skeleton.height,
                        fps: skeleton.skeleton.fps || 30,
                        skins: skeleton.skins.map((s) => s.name),
                        animations: Object.keys(skeleton.animations),
                    },
                },
            ],
            children: [],
            tags: ['spine', 'skeleton'],
            active: true,
        };
        // Create bone hierarchy
        const boneEntities = this.createBoneHierarchy(skeleton.bones);
        rootEntity.children = boneEntities;
        // Add slots and attachments
        for (const slot of skeleton.slots) {
            this.addSlotToEntity(rootEntity, slot, skeleton.skins);
        }
        // Add IK constraints
        if (skeleton.ik) {
            for (const ik of skeleton.ik) {
                rootEntity.components.push({
                    type: 'IKConstraint',
                    data: {
                        name: ik.name,
                        bones: ik.bones,
                        target: ik.target,
                        mix: (_a = ik.mix) !== null && _a !== void 0 ? _a : 1,
                        softness: (_b = ik.softness) !== null && _b !== void 0 ? _b : 0,
                        bendPositive: (_c = ik.bendPositive) !== null && _c !== void 0 ? _c : true,
                    },
                });
            }
        }
        // Add animations
        for (const [animName, anim] of Object.entries(skeleton.animations)) {
            const animationData = this.convertAnimation(animName, anim, skeleton);
            rootEntity.components.push({
                type: 'Animation',
                data: animationData,
            });
        }
        entities.push(rootEntity);
        return {
            id: this.generateId(),
            name,
            entities,
            settings: {
                width: skeleton.skeleton.width || 800,
                height: skeleton.skeleton.height || 600,
                backgroundColor: '#1a1a2e',
                fps: skeleton.skeleton.fps || 30,
            },
        };
    }
    convertSkeletonToParsed(skeleton) {
        var _a, _b;
        const entities = [];
        // Create bone entities
        for (const bone of skeleton.bones) {
            entities.push({
                id: bone.name,
                name: bone.name,
                type: 'Bone',
                x: bone.x || 0,
                y: bone.y || 0,
                width: bone.length || 0,
                height: 4,
                rotation: bone.rotation || 0,
                scaleX: (_a = bone.scaleX) !== null && _a !== void 0 ? _a : 1,
                scaleY: (_b = bone.scaleY) !== null && _b !== void 0 ? _b : 1,
                visible: true,
                components: [],
                children: [],
                properties: {
                    parent: bone.parent,
                    length: bone.length,
                    transform: bone.transform,
                    shearX: bone.shearX,
                    shearY: bone.shearY,
                    color: bone.color,
                },
            });
        }
        return {
            name: 'Spine Skeleton',
            width: skeleton.skeleton.width || 800,
            height: skeleton.skeleton.height || 600,
            backgroundColor: '#1a1a2e',
            layers: [
                {
                    name: 'Skeleton',
                    type: 'object',
                    visible: true,
                    opacity: 1,
                    offsetX: 0,
                    offsetY: 0,
                    zIndex: 0,
                    properties: {},
                },
            ],
            entities,
            properties: {
                version: skeleton.skeleton.spine,
                fps: skeleton.skeleton.fps,
                skins: skeleton.skins.map((s) => s.name),
                animations: Object.keys(skeleton.animations),
            },
        };
    }
    // ----------------------------------------
    // Bone Hierarchy
    // ----------------------------------------
    createBoneHierarchy(bones) {
        var _a, _b;
        const boneMap = new Map();
        const rootBones = [];
        // Create all bone entities
        for (const bone of bones) {
            const entity = {
                id: bone.name,
                name: bone.name,
                components: [
                    {
                        type: 'Transform',
                        data: {
                            position: { x: bone.x || 0, y: bone.y || 0 },
                            rotation: bone.rotation || 0,
                            scale: { x: (_a = bone.scaleX) !== null && _a !== void 0 ? _a : 1, y: (_b = bone.scaleY) !== null && _b !== void 0 ? _b : 1 },
                        },
                    },
                    {
                        type: 'Bone',
                        data: {
                            length: bone.length || 0,
                            shearX: bone.shearX || 0,
                            shearY: bone.shearY || 0,
                            transform: bone.transform || 'normal',
                            color: bone.color,
                        },
                    },
                ],
                children: [],
                tags: ['bone'],
                active: true,
            };
            boneMap.set(bone.name, entity);
        }
        // Build hierarchy
        for (const bone of bones) {
            const entity = boneMap.get(bone.name);
            if (bone.parent) {
                const parent = boneMap.get(bone.parent);
                if (parent) {
                    if (!parent.children)
                        parent.children = [];
                    parent.children.push(entity);
                }
                else {
                    rootBones.push(entity);
                }
            }
            else {
                rootBones.push(entity);
            }
        }
        return rootBones;
    }
    addSlotToEntity(entity, slot, skins) {
        // Find bone to attach to
        const bone = this.findBoneEntity(entity, slot.bone);
        if (!bone)
            return;
        const slotComponent = {
            type: 'Slot',
            data: {
                name: slot.name,
                bone: slot.bone,
                attachment: slot.attachment,
                color: slot.color,
                dark: slot.dark,
                blend: slot.blend || 'normal',
            },
        };
        bone.components.push(slotComponent);
        // Add default attachment from default skin
        const defaultSkin = skins.find((s) => s.name === 'default');
        if (defaultSkin && slot.attachment) {
            const attachments = defaultSkin.attachments[slot.name];
            if (attachments && attachments[slot.attachment]) {
                const attachment = attachments[slot.attachment];
                bone.components.push({
                    type: 'Attachment',
                    data: {
                        name: slot.attachment,
                        ...attachment,
                    },
                });
            }
        }
    }
    findBoneEntity(entity, boneName) {
        var _a;
        if (entity.name === boneName)
            return entity;
        for (const child of (_a = entity.children) !== null && _a !== void 0 ? _a : []) {
            const found = this.findBoneEntity(child, boneName);
            if (found)
                return found;
        }
        return null;
    }
    // ----------------------------------------
    // Animation Conversion
    // ----------------------------------------
    convertAnimation(name, anim, skeleton) {
        const duration = this.calculateAnimationDuration(anim);
        return {
            name,
            duration,
            fps: skeleton.skeleton.fps || 30,
            bones: anim.bones,
            slots: anim.slots,
            ik: anim.ik,
            transform: anim.transform,
            path: anim.path,
            deform: anim.deform,
            drawOrder: anim.drawOrder,
            events: anim.events,
        };
    }
    calculateAnimationDuration(anim) {
        let maxTime = 0;
        const checkKeyframes = (keyframes) => {
            if (keyframes) {
                for (const kf of keyframes) {
                    if (kf.time > maxTime)
                        maxTime = kf.time;
                }
            }
        };
        // Check bones
        if (anim.bones) {
            for (const timeline of Object.values(anim.bones)) {
                checkKeyframes(timeline.rotate);
                checkKeyframes(timeline.translate);
                checkKeyframes(timeline.scale);
                checkKeyframes(timeline.shear);
            }
        }
        // Check slots
        if (anim.slots) {
            for (const timeline of Object.values(anim.slots)) {
                checkKeyframes(timeline.color);
                checkKeyframes(timeline.twoColor);
            }
        }
        // Check events
        checkKeyframes(anim.events);
        // Check draw order
        checkKeyframes(anim.drawOrder);
        return maxTime;
    }
    // ----------------------------------------
    // Atlas Parsing
    // ----------------------------------------
    parseAtlas(content) {
        const atlas = { pages: [] };
        const lines = content.split(/\r?\n/);
        let currentPage = null;
        let i = 0;
        while (i < lines.length) {
            const line = lines[i].trim();
            if (line === '') {
                i++;
                continue;
            }
            // Check if this is a page header (no colon, no indentation)
            if (!line.includes(':') && lines[i].charAt(0) !== ' ' && lines[i].charAt(0) !== '\t') {
                currentPage = {
                    name: line,
                    size: { width: 0, height: 0 },
                    format: 'RGBA8888',
                    filter: { min: 'Linear', mag: 'Linear' },
                    repeat: 'none',
                    regions: [],
                };
                atlas.pages.push(currentPage);
                i++;
                // Parse page properties
                while (i < lines.length && lines[i].includes(':')) {
                    const propLine = lines[i].trim();
                    const colonIndex = propLine.indexOf(':');
                    const key = propLine.substring(0, colonIndex).trim();
                    const value = propLine.substring(colonIndex + 1).trim();
                    switch (key) {
                        case 'size': {
                            const [w, h] = value.split(',').map((s) => parseInt(s.trim()));
                            currentPage.size = { width: w, height: h };
                            break;
                        }
                        case 'format':
                            currentPage.format = value;
                            break;
                        case 'filter': {
                            const [min, mag] = value.split(',').map((s) => s.trim());
                            currentPage.filter = { min, mag };
                            break;
                        }
                        case 'repeat':
                            currentPage.repeat = value;
                            break;
                        case 'pma':
                            currentPage.pma = value === 'true';
                            break;
                    }
                    i++;
                }
            }
            else if (currentPage && !line.includes(':')) {
                // This is a region name
                const region = {
                    name: line,
                    bounds: { x: 0, y: 0, width: 0, height: 0 },
                };
                currentPage.regions.push(region);
                i++;
                // Parse region properties
                while (i < lines.length && lines[i].includes(':')) {
                    const propLine = lines[i].trim();
                    const colonIndex = propLine.indexOf(':');
                    const key = propLine.substring(0, colonIndex).trim();
                    const value = propLine.substring(colonIndex + 1).trim();
                    switch (key) {
                        case 'bounds':
                        case 'xy': {
                            const parts = value.split(',').map((s) => parseInt(s.trim()));
                            if (key === 'bounds' && parts.length === 4) {
                                region.bounds = { x: parts[0], y: parts[1], width: parts[2], height: parts[3] };
                            }
                            else if (parts.length >= 2) {
                                region.bounds.x = parts[0];
                                region.bounds.y = parts[1];
                            }
                            break;
                        }
                        case 'size': {
                            const [w, h] = value.split(',').map((s) => parseInt(s.trim()));
                            region.bounds.width = w;
                            region.bounds.height = h;
                            break;
                        }
                        case 'orig':
                        case 'offsets': {
                            const parts = value.split(',').map((s) => parseInt(s.trim()));
                            if (parts.length >= 4) {
                                region.offsets = { x: parts[0], y: parts[1], width: parts[2], height: parts[3] };
                            }
                            break;
                        }
                        case 'rotate':
                            region.rotate =
                                value === 'true' || value === '90'
                                    ? true
                                    : value === '270'
                                        ? 270
                                        : value === '180'
                                            ? 180
                                            : false;
                            break;
                        case 'index':
                            region.index = parseInt(value);
                            break;
                    }
                    i++;
                }
            }
            else {
                i++;
            }
        }
        return atlas;
    }
    // ----------------------------------------
    // Utilities
    // ----------------------------------------
    async readFile(path) {
        var _a;
        if (typeof globalThis.process !== 'undefined' && ((_a = globalThis.process.versions) === null || _a === void 0 ? void 0 : _a.node)) {
            const fs = await import('fs');
            return fs.readFileSync(path, 'utf-8');
        }
        else if (typeof fetch !== 'undefined') {
            const response = await fetch(path);
            return response.text();
        }
        throw new Error('Unable to read file in current environment');
    }
    extractFileName(path) {
        const name = path.substring(path.lastIndexOf('/') + 1);
        const dotIndex = name.lastIndexOf('.');
        return dotIndex > 0 ? name.substring(0, dotIndex) : name;
    }
}
// ============================================================
// Factory Function
// ============================================================
function createSpineImporter() {
    return new SpineImporter();
}

exports.SpineImporter = SpineImporter;
exports.createSpineImporter = createSpineImporter;
//# sourceMappingURL=SpineImporter.js.map
