/**
 * Aseprite Importer Plugin -- registers an importer for .ase/.aseprite files.
 *
 * Converts parsed Aseprite data into PixelWeaver's internal state:
 * canvas dimensions, layers, frames with pixel data, and palette.
 */

import type { PluginModule } from '../../../src/lib/core/plugin-loader.js';
import type { PluginAPI } from '../../../src/lib/core/plugin-types.js';
import FileImage from '~icons/lucide/file-image';
import { parseAseprite } from './aseprite-parser.js';
import type { Palette } from '../../../src/lib/color/palette.js';
import type { AsepriteLayer } from './aseprite-parser.js';
import { rgbToHex } from '../../../src/lib/color/color-utils.js';

/**
 * Build a PixelWeaver layer tree from the flat Aseprite layer list.
 * Aseprite layers use childLevel to indicate nesting depth; we reconstruct
 * the parent-child relationships and create PW layers accordingly.
 *
 * Returns a map from Aseprite layer index to PixelWeaver layer ID.
 */
function createLayerTree(api: PluginAPI, aseLayers: AsepriteLayer[]): Map<number, string> {
  const indexToId = new Map<number, string>();

  // Track the group stack: each entry is the PW group layer ID at that depth.
  // childLevel 0 = root, childLevel 1 = inside a depth-0 group, etc.
  const groupStack: string[] = [];

  for (let i = 0; i < aseLayers.length; i++) {
    const aseLayer = aseLayers[i];
    if (!aseLayer) continue;

    // Trim the group stack to match the current child level
    while (groupStack.length > aseLayer.childLevel) {
      groupStack.pop();
    }

    const parentId = groupStack.length > 0 ? groupStack[groupStack.length - 1] : undefined;
    const isVisible = (aseLayer.flags & 1) !== 0;
    // Aseprite opacity is 0-255, PixelWeaver uses 0-100
    const pwOpacity = Math.round((aseLayer.opacity / 255) * 100);

    // Build options object, omitting parentId entirely when undefined
    // (exactOptionalPropertyTypes disallows passing { parentId: undefined })
    const layerOptions = parentId !== undefined ? { parentId } : {};

    if (aseLayer.type === 1) {
      // Group layer
      const groupId = api.addGroup(aseLayer.name, layerOptions);
      api.setLayerVisibility(groupId, isVisible);
      api.setLayerOpacity(groupId, pwOpacity);
      indexToId.set(i, groupId);
      groupStack.push(groupId);
    } else {
      // Pixel layer (type 0) or tilemap (type 2, treated as pixel)
      const layerId = api.addLayer(aseLayer.name, layerOptions);
      api.setLayerVisibility(layerId, isVisible);
      api.setLayerOpacity(layerId, pwOpacity);
      indexToId.set(i, layerId);
    }
  }

  return indexToId;
}

export const asepriteImporterPlugin: PluginModule = {
  name: 'builtin/import-aseprite',
  version: '1.0.0',
  description: 'Import Aseprite .ase/.aseprite files',

  register(api) {
    api.addCommand('import_aseprite', {
      tier: 'project',
      execute() { /* Will be wired to file picker */ },
      undo() {},
      describe() { return 'Import Aseprite file'; },
      label: 'Aseprite (.ase/.aseprite)',
      category: 'File',
      icon: FileImage,
    });
    api.addMenuItem('menu:file:import:aseprite', {
      commandId: 'import_aseprite',
      menuPath: 'file/import',
      group: 'import',
      order: 10,
      label: 'Aseprite (.ase/.aseprite)',
    });

    api.addImporter('aseprite', {
      label: 'Aseprite File',
      extensions: ['ase', 'aseprite'],

      async import(data) {
        const buffer = data instanceof File ? await data.arrayBuffer() : data;
        const aseFile = await parseAseprite(buffer);

        // 1. Set canvas dimensions
        api.setCanvasSize(aseFile.width, aseFile.height);

        // 2. Clear existing layers and frames to start fresh
        api.deserializeLayers({ layers: [], activeLayerId: '' });
        api.deserializeFrames({
          frames: [],
          currentFrameIndex: 0,
          globalFps: 12,
          originX: 0,
          originY: 0,
        });

        // 3. Create PixelWeaver layers from the Aseprite layer list
        const layerIndexToId = createLayerTree(api, aseFile.layers);

        // 4. Create frames and populate pixel data
        for (let f = 0; f < aseFile.frames.length; f++) {
          const aseFrame = aseFile.frames[f];
          if (!aseFrame) continue;
          const frameIndex = api.addFrame({ afterIndex: f - 1 });

          // Set per-frame duration if specified
          if (aseFrame.duration > 0) {
            api.setFrameDuration(frameIndex, aseFrame.duration);
          }

          // Copy cel pixel data into each layer's PixelBuffer for this frame
          for (const cel of aseFrame.cels) {
            const layerId = layerIndexToId.get(cel.layerIndex);
            if (!layerId) continue; // layer not mapped (e.g., unsupported type)
            if (cel.pixels.length === 0) continue; // empty or unsupported cel

            // Create a full-canvas-sized PixelBuffer and place cel pixels at (cel.x, cel.y)
            const pixelBuffer = api.createPixelBuffer(aseFile.width, aseFile.height);

            for (let py = 0; py < cel.height; py++) {
              for (let px = 0; px < cel.width; px++) {
                const srcIdx = (py * cel.width + px) * 4;
                const destX = cel.x + px;
                const destY = cel.y + py;
                pixelBuffer.setPixel(
                  destX,
                  destY,
                  cel.pixels[srcIdx] ?? 0,
                  cel.pixels[srcIdx + 1] ?? 0,
                  cel.pixels[srcIdx + 2] ?? 0,
                  cel.pixels[srcIdx + 3] ?? 0,
                );
              }
            }

            api.setFramePixelData(frameIndex, layerId, pixelBuffer);
          }
        }

        // Select the first frame
        if (api.getFrames().length > 0) {
          api.setCurrentFrame(0);
        }

        // 5. Set the project palette from Aseprite colors
        if (aseFile.palette.length > 0) {
          const palette: Palette = {
            name: 'Imported (Aseprite)',
            colors: aseFile.palette
              .filter((c) => c.a > 0) // skip fully transparent entries
              .map((c) => rgbToHex(c.r, c.g, c.b)),
          };
          // Deduplicate colors while preserving order
          palette.colors = [...new Set(palette.colors)];
          api.setProjectPalette(palette);
        }
      },
    });
  },
};
