/**
 * import.ts — Main import pipeline: ContentPack / ExportResult / WorldProject → WorldProject
 *
 * ## Import Pipeline Architecture
 *
 * The import pipeline follows a three-stage process:
 *
 * 1. **Format Detection** (`detectImportFormat`)
 *    Inspects the shape of parsed JSON to determine which import format it matches.
 *    Supported formats: `world-project`, `content-pack`, `export-result`, `project-bundle`.
 *
 * 2. **Conversion** (per-domain converter functions)
 *    Each domain (zones, entities, items, dialogues, etc.) has a dedicated import
 *    converter in its own module (e.g. `import-zones.ts`, `import-entities.ts`).
 *    Converters transform engine-format data back into WorldProject domain objects
 *    and produce {@link FidelityEntry} records documenting any data loss.
 *
 * 3. **Assembly** (`importProject` / `importFromContentPack` / `importFromExportResult`)
 *    Combines converted domains into a complete {@link WorldProject}, infers missing
 *    metadata (mode, connections, spawn points), runs validation, and builds the
 *    {@link FidelityReport}.
 *
 * ## How to Add a New Import Format
 *
 * 1. Add the new format name to the {@link ImportFormat} union type.
 * 2. Add a detection clause in {@link detectImportFormat} that identifies the new
 *    format by its structural shape (check for unique top-level keys).
 * 3. Write a converter function (e.g. `importFromMyFormat`) that accepts the raw
 *    data and returns an {@link ImportResult}. Use per-domain importers where possible.
 * 4. Add a branch in {@link importProject} that dispatches to your converter.
 * 5. Re-export any new public functions/types from `index.ts`.
 *
 * ## Extension Points
 *
 * - {@link detectImportFormat} — Add detection logic for new shapes.
 * - Per-domain converters (`importZones`, `importEntities`, etc.) — Reuse or extend
 *   for formats that share the engine's domain schemas.
 * - {@link importProject} — The main entry point; add dispatch branches here.
 * - {@link ImportFormat} — The union type listing all recognized formats.
 * - {@link FidelityEntry} / {@link FidelityDomain} (in `fidelity.ts`) — Extend with
 *   new domains if your format introduces data categories not yet tracked.
 *
 * @module import
 */
import { validateProject, isValidMode } from '@world-forge/schema';
import { buildFidelityReport } from './fidelity.js';
import { importZones } from './import-zones.js';
import { importDistricts } from './import-districts.js';
import { importEntities } from './import-entities.js';
import { importItems } from './import-items.js';
import { importDialogues } from './import-dialogues.js';
import { importPlayerTemplate } from './import-player-template.js';
import { importBuildCatalog } from './import-build-catalog.js';
import { importProgressionTrees } from './import-progression-trees.js';
// EB-012: Reverse maps must stay in sync with GENRE_MAP / DIFFICULTY_MAP in convert-pack.ts.
// When new genres or difficulties are added to the forward maps, add their reverse entries here.
/** @internal Exported for drift-guard tests only (AIR-A-005/006). */
export const REVERSE_GENRE = {
    fantasy: 'fantasy', 'sci-fi': 'sci-fi', cyberpunk: 'cyberpunk',
    horror: 'horror', mystery: 'detective', western: 'western',
    pirate: 'pirate', 'post-apocalyptic': 'zombie', historical: 'historical',
};
/** @internal Exported for drift-guard tests only (AIR-A-005/006). */
export const REVERSE_DIFFICULTY = {
    beginner: 'beginner', intermediate: 'intermediate', advanced: 'advanced',
};
/** Infer authoring mode from project content when mode is not set. */
export function inferMode(project) {
    // If mode is explicitly set and valid, use it
    if (project.mode && isValidMode(project.mode))
        return project.mode;
    const kinds = new Set(project.connections.map((c) => c.kind).filter(Boolean));
    const area = project.map.gridWidth * project.map.gridHeight;
    // Connection-kind heuristics
    if (kinds.has('channel') || kinds.has('route'))
        return 'ocean';
    if (kinds.has('warp') || kinds.has('docking'))
        return 'space';
    // Tag-based heuristics for wilderness
    const allZoneTags = project.zones.flatMap((z) => z.tags ?? []);
    if (kinds.has('trail') && (allZoneTags.includes('camp') || allZoneTags.includes('wild')))
        return 'wilderness';
    // Size-based heuristics
    if (area <= 400)
        return 'interior'; // small grid (e.g. 20×15 = 300)
    if (area >= 4000)
        return 'world'; // large grid (e.g. 80×60 = 4800)
    // Fallback
    return 'dungeon';
}
/** Detect the format of parsed JSON data. */
export function detectImportFormat(data) {
    if (typeof data !== 'object' || data === null || Array.isArray(data))
        return null;
    const obj = data;
    // ProjectBundle has bundleVersion + project (must check before WorldProject)
    if ('bundleVersion' in obj && 'project' in obj
        && typeof obj.project === 'object' && obj.project !== null
        && 'map' in obj.project)
        return 'project-bundle';
    // WorldProject has map + entityPlacements + zones (all must be correct types)
    if ('map' in obj && 'entityPlacements' in obj && 'zones' in obj
        && typeof obj.map === 'object' && obj.map !== null
        && Array.isArray(obj.zones)
        && Array.isArray(obj.entityPlacements))
        return 'world-project';
    // ExportResult has contentPack + manifest (both must be objects)
    if ('contentPack' in obj && 'manifest' in obj
        && typeof obj.contentPack === 'object' && obj.contentPack !== null
        && typeof obj.manifest === 'object' && obj.manifest !== null)
        return 'export-result';
    // ContentPack has entities + zones arrays but no map
    if ('entities' in obj && 'zones' in obj && !('map' in obj)
        && Array.isArray(obj.entities)
        && Array.isArray(obj.zones))
        return 'content-pack';
    return null;
}
/** Import from any supported format. */
export function importProject(data) {
    const format = detectImportFormat(data);
    // EB-010: Explicit error path for null/unrecognized format
    if (format === null) {
        const message = 'Unrecognized file format. Expected a WorldProject (.map + .zones + .entityPlacements), ContentPack (.entities + .zones), ExportResult (.contentPack + .manifest), or ProjectBundle (.bundleVersion + .project). Check that your JSON structure matches one of these shapes.';
        return {
            success: false,
            message,
            errors: [{ path: 'import', message }],
        };
    }
    if (format === 'world-project') {
        const project = data;
        const validation = validateProject(project);
        const warnings = validation.valid ? [] : validation.errors.map((e) => `${e.path}: ${e.message}`);
        return { success: true, project, format: 'world-project', warnings, lossless: true, fidelityReport: buildFidelityReport([]) };
    }
    if (format === 'export-result') {
        return importFromExportResult(data);
    }
    if (format === 'content-pack') {
        return importFromContentPack(data);
    }
    // project-bundle: extract the embedded project
    if (format === 'project-bundle') {
        const bundle = data;
        return importProject(bundle.project);
    }
    // Exhaustive: this should never be reached
    const message = `Internal error: unhandled import format '${format}'.`;
    return { success: false, message, errors: [{ path: 'import', message }] };
}
/** Import from an ExportResult (has contentPack + manifest + packMeta). */
export function importFromExportResult(result, projectName) {
    const meta = result.packMeta;
    const imported = importFromContentPack(result.contentPack, projectName ?? meta?.name, meta);
    imported.format = 'export-result';
    // Recover mode from PackMetadata tags if present (e.g. "mode:ocean")
    if (meta?.tags) {
        const modeTag = meta.tags.find((t) => t.startsWith('mode:'));
        if (modeTag) {
            const modeValue = modeTag.slice(5); // strip "mode:"
            if (isValidMode(modeValue)) {
                imported.project.mode = modeValue;
            }
        }
    }
    // Recover assets + bindings from ExportResult (not available in raw ContentPack)
    if (result.assets && result.assets.length > 0) {
        imported.project.assets = result.assets;
        if (result.assetBindings) {
            applyAssetBindings(imported.project, result.assetBindings);
        }
        imported.fidelityReport.entries.push({
            level: 'lossless', domain: 'assets', severity: 'info',
            message: `${result.assets.length} asset(s) recovered from export result`,
            reason: 'assets-recovered',
        });
    }
    // Recover asset packs from ExportResult
    if (result.assetPacks && result.assetPacks.length > 0) {
        imported.project.assetPacks = result.assetPacks;
        imported.fidelityReport.entries.push({
            level: 'lossless', domain: 'packs', severity: 'info',
            message: `${result.assetPacks.length} asset pack(s) recovered from export result`,
            reason: 'asset-packs-recovered',
        });
    }
    return imported;
}
/** Apply asset bindings from an ExportResult back onto a WorldProject. */
function applyAssetBindings(project, bindings) {
    if (bindings.zones) {
        for (const z of project.zones) {
            const b = bindings.zones[z.id];
            if (b) {
                if (b.backgroundId)
                    z.backgroundId = b.backgroundId;
                if (b.tilesetId)
                    z.tilesetId = b.tilesetId;
            }
        }
    }
    if (bindings.entities) {
        for (const e of project.entityPlacements) {
            const b = bindings.entities[e.entityId];
            if (b) {
                if (b.portraitId)
                    e.portraitId = b.portraitId;
                if (b.spriteId)
                    e.spriteId = b.spriteId;
            }
        }
    }
    if (bindings.items) {
        for (const i of project.itemPlacements) {
            const b = bindings.items[i.itemId];
            if (b) {
                if (b.iconId)
                    i.iconId = b.iconId;
            }
        }
    }
    if (bindings.landmarks) {
        for (const l of project.landmarks) {
            const b = bindings.landmarks[l.id];
            if (b) {
                if (b.iconId)
                    l.iconId = b.iconId;
            }
        }
    }
}
/** Import from a ContentPack (lossy — zones lose grid positions, entities lose zones). */
export function importFromContentPack(pack, projectName, meta) {
    const allFidelity = [];
    // 1. Import each domain (destructure fidelity from each converter)
    const { zones, fidelity: zoneFidelity } = importZones(pack.zones);
    const { districts, fidelity: districtFidelity } = importDistricts(pack.districts);
    const { placements: entityPlacements, warnings: entityWarnings, fidelity: entityFidelity } = importEntities(pack.entities, zones.map((z) => z.id));
    const { placements: itemPlacements, warnings: itemWarnings, fidelity: itemFidelity } = importItems(pack.items, zones.map((z) => z.id));
    const { dialogues, fidelity: dialogueFidelity } = importDialogues(pack.dialogues ?? []);
    const { template: playerTemplate, fidelity: playerFidelity } = importPlayerTemplate(pack.playerTemplate);
    const { catalog: buildCatalog, fidelity: buildFidelity } = importBuildCatalog(pack.buildCatalog);
    const { trees: progressionTrees, fidelity: treeFidelity } = importProgressionTrees(pack.progressionTrees ?? []);
    // Collect all domain fidelity entries
    allFidelity.push(...zoneFidelity, ...districtFidelity, ...entityFidelity, ...itemFidelity, ...dialogueFidelity, ...playerFidelity, ...buildFidelity, ...treeFidelity);
    // 2. Cross-reference districts → zones: set parentDistrictId
    for (const d of districts) {
        for (const zid of d.zoneIds) {
            const zone = zones.find((z) => z.id === zid);
            if (zone)
                zone.parentDistrictId = d.id;
        }
    }
    // 3. Generate connections from zone neighbor pairs (deduplicated, bidirectional)
    const connections = [];
    const seen = new Set();
    for (const zone of zones) {
        for (const nid of zone.neighbors) {
            const key = [zone.id, nid].sort().join('|');
            if (!seen.has(key)) {
                seen.add(key);
                connections.push({ fromZoneId: zone.id, toZoneId: nid, bidirectional: true });
            }
        }
    }
    if (connections.length > 0) {
        allFidelity.push({
            level: 'lossless', domain: 'world', severity: 'info',
            message: `${connections.length} connection(s) reconstructed from zone neighbor data`,
            reason: 'connections-reconstructed',
        });
    }
    // 4. Compute map dimensions from auto-layout bounds (40×30 minimum floor)
    let maxX = 0, maxY = 0;
    for (const z of zones) {
        maxX = Math.max(maxX, z.gridX + z.gridWidth + 4);
        maxY = Math.max(maxY, z.gridY + z.gridHeight + 4);
    }
    maxX = Math.max(maxX, 40);
    maxY = Math.max(maxY, 30);
    // 5. Create spawn point
    if (zones.length === 0) {
        allFidelity.push({
            level: 'dropped', domain: 'zones', severity: 'warning',
            message: 'No zones found — cannot create spawn point. The imported world will have no navigable areas.',
            reason: 'no-zones-no-spawn',
        });
    }
    const spawnZone = zones[0];
    const spawnPointId = (playerTemplate?.spawnPointId && playerTemplate.spawnPointId !== '') ? playerTemplate.spawnPointId : 'imported-spawn';
    const spawnPoints = spawnZone ? [{
            id: spawnPointId,
            zoneId: spawnZone.id,
            gridX: spawnZone.gridX + 1,
            gridY: spawnZone.gridY + 1,
            isDefault: true,
        }] : [];
    // Update player template spawnPointId if it was created fresh (check empty string too)
    if (playerTemplate && (!playerTemplate.spawnPointId || playerTemplate.spawnPointId === '') && spawnPoints.length > 0) {
        playerTemplate.spawnPointId = spawnPointId;
    }
    // 6. Recover metadata from PackMetadata if available
    // EB-015: Null-coalescing for meta.genres and meta.tones before .map()
    const genre = meta?.genres?.[0] ? (REVERSE_GENRE[meta.genres[0]] ?? meta.genres[0]) : 'fantasy';
    const tones = (meta?.tones ?? []).length > 0 ? (meta?.tones ?? []).map(String) : ['atmospheric'];
    const difficulty = meta?.difficulty ? (REVERSE_DIFFICULTY[meta.difficulty] ?? 'intermediate') : 'intermediate';
    const narratorTone = meta?.narratorTone ?? '';
    // 7. Build the WorldProject
    const project = {
        id: `imported-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
        name: projectName ?? 'Imported World',
        description: meta?.description ?? 'Imported from engine content pack.',
        version: '0.1.0',
        genre,
        tones,
        difficulty,
        narratorTone,
        map: { id: 'map-1', name: projectName ?? 'Imported World', description: '', gridWidth: maxX, gridHeight: maxY, tileSize: 32 },
        zones,
        connections,
        districts,
        landmarks: [],
        factionPresences: pack.factionPresences ?? [],
        pressureHotspots: pack.pressureHotspots ?? [],
        dialogues,
        playerTemplate,
        buildCatalog,
        progressionTrees,
        entityPlacements,
        itemPlacements,
        encounterAnchors: pack.encounterAnchors ?? [],
        spawnPoints,
        craftingStations: [],
        marketNodes: [],
        tilesets: [],
        tileLayers: [],
        props: [],
        propPlacements: [],
        ambientLayers: [],
        assets: [],
        assetPacks: [],
    };
    // 7b. Infer and apply mode if not set
    if (!project.mode) {
        const inferred = inferMode(project);
        project.mode = inferred;
        allFidelity.push({
            level: 'approximated', domain: 'world', severity: 'info',
            message: `Authoring mode inferred as '${inferred}' from project content`,
            reason: 'mode-inferred',
        });
    }
    // 8. Add structural fidelity entries
    allFidelity.push({
        level: 'dropped', domain: 'world', severity: 'warning',
        message: 'Visual layers not imported (tilesets, props, ambient)',
        reason: 'visual-layers-dropped',
    });
    allFidelity.push({
        level: 'dropped', domain: 'assets', severity: 'warning',
        message: 'Asset manifest not available in ContentPack format',
        reason: 'assets-dropped',
    });
    allFidelity.push({
        level: 'dropped', domain: 'packs', severity: 'warning',
        message: 'Asset packs not available in ContentPack format',
        reason: 'asset-packs-dropped',
    });
    // 9. Build fidelity report
    const fidelityReport = buildFidelityReport(allFidelity);
    // 10. Derive backwards-compatible warnings from fidelity + entity/item warnings
    const warnings = [...entityWarnings, ...itemWarnings];
    if (zones.length > 0)
        warnings.push('Zone grid positions auto-generated (original layout unknown)');
    if (pack.entities.length > 0)
        warnings.push('Entity zone placements reconstructed (original zones unknown)');
    if (pack.items.length > 0)
        warnings.push('Item zone placements reconstructed (original zones unknown)');
    warnings.push('Visual layers not imported (tilesets, props, ambient)');
    // 11. Validate and surface any remaining issues
    const validation = validateProject(project);
    if (!validation.valid) {
        for (const err of validation.errors) {
            warnings.push(`Validation: ${err.path} — ${err.message}`);
        }
    }
    const lossless = fidelityReport.summary.approximated === 0 && fidelityReport.summary.dropped === 0;
    return { success: true, project, format: 'content-pack', warnings, lossless, fidelityReport };
}
//# sourceMappingURL=import.js.map