package car

import (
	"bytes"
	"compress/flate"
	"compress/gzip"
	"compress/zlib"
	"encoding/binary"
	"fmt"
	"image"
	"image/color"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/apex/log"
	"github.com/blacktop/go-macho/types"
	"github.com/blacktop/ipsw/internal/magic"
	"github.com/blacktop/ipsw/pkg/comp"
	"github.com/blacktop/lzfse-cgo"
)

const (
	PixFmtARGB    = "ARGB" // Color image
	PixFmtARGB16  = "RGBW" // Deep color image
	PixFmtRGB555  = "RGB5" // Packed 16-bit per pixel opaque image
	PixFmtGray    = "GA8 " // Gray scale image with alpha
	PixFmtGray16  = "GA16" // Deep gray scale image with alpha
	PixFmtPDF     = "PDF " // PDF raw bytes
	PixFmtJPEG    = "JPEG" // JPEG raw bytes
	PixFmtHEIF    = "HEIF" // HEIF raw bytes
	PixFmtRawData = "DATA" // Raw bytes
)

type compressionType uint32

const (
	Uncompressed compressionType = 0
	RLE          compressionType = 1
	ZIP          compressionType = 2
	LZVN         compressionType = 3
	LZFSE        compressionType = 4
	JPEGLZFSE    compressionType = 5
	BlurredImage compressionType = 6
	ASTCImage    compressionType = 7
	PaletteImage compressionType = 8
	HEVC         compressionType = 9
	DeepmapLZFSE compressionType = 10
	Deepmap2     compressionType = 11
	DXTC         compressionType = 12
)

type csiBitmapFlags uint32

func (f csiBitmapFlags) ChunksFollow() bool {
	return types.ExtractBits(uint64(f), 0, 1) == 1
}
func (f csiBitmapFlags) IsOpaque() bool {
	return types.ExtractBits(uint64(f), 1, 1) == 1
}
func (f csiBitmapFlags) String() string {
	return fmt.Sprintf("chunks_follow: %t, is_opaque: %t", f.ChunksFollow(), f.IsOpaque())
}

type csiBitmap struct {
	Signature [4]byte // 'PELM'
	Flags     csiBitmapFlags
	Encoding  compressionType
	Length    uint32
	// Data      []byte
}

type csiBitmapChunk struct {
	Signature [4]byte // 'PECH' PELM Chunk
	Flags     uint32  // always 0
	Version   uint32
	Rows      uint32
	Length    uint32
	// Data      []byte
}

type csiRawData struct {
	Signature [4]byte // 'RAWD'
	Flags     uint32
	Length    uint32
	// Data      []byte
}

type csiJpegLZFSEData struct {
	Version           uint32
	ChucksFollowing   uint32
	LzfseAlphaSize    uint32
	LzfseDataRowBytes uint32
	JpegDataSize      uint32
}

type csiASTCData struct {
	Version      uint32 // 0 == raw ATSC Data; 1 == lzfse compressed ATSC Data
	DataSize     uint32
	AstcDataSize uint32
	// Data      []byte
}

type csiHEVCData struct {
	Version      uint32
	HevcDataSize uint32
}

type csiDeepmapData struct {
	Version         uint32
	PixelFormat     uint32
	CompressedBytes uint64
}

type csiDeepmap2Data struct {
	Version  uint32
	Encoding compressionType
	Length   uint64
}

type deepmapPixelFormat uint8

const (
	ImageDeepmapPixelFormatG8      deepmapPixelFormat = 0x01
	ImageDeepmapPixelFormatGA8     deepmapPixelFormat = 0x02
	ImageDeepmapPixelFormatRGB8    deepmapPixelFormat = 0x03
	ImageDeepmapPixelFormatRGBA8   deepmapPixelFormat = 0x04
	ImageDeepmapPixelFormatRGBA16  deepmapPixelFormat = 0x0A // 16-bit per channel RGBA
	ImageDeepmapPixelFormatG16F    deepmapPixelFormat = 0x11
	ImageDeepmapPixelFormatGA16F   deepmapPixelFormat = 0x12
	ImageDeepmapPixelFormatRGB16F  deepmapPixelFormat = 0x13
	ImageDeepmapPixelFormatRGBA16F deepmapPixelFormat = 0x14
)

type deepmapCompressionMethod uint8

const (
	ImageDeepmapCompressionNone     deepmapCompressionMethod = 1
	ImageDeepmapCompressionDefault  deepmapCompressionMethod = 2
	ImageDeepmapCompressionLossless deepmapCompressionMethod = 3
	ImageDeepmapCompressionPalette  deepmapCompressionMethod = 4
)

type deepmap struct {
	Signature         [4]byte // 'dmap'
	CompressionMethod deepmapCompressionMethod
	Scale             uint8
	Unknown           uint8 // 10 ?
	PixelFormat       deepmapPixelFormat
	CompressedBlock   uint32
}

type deepmap2 struct {
	Signature         [4]byte // 'dmp2'
	Scale             uint8
	BlobVersion       uint8 // 1
	PixelFormat       deepmapPixelFormat
	CompressionMethod deepmapCompressionMethod
	Width             uint16
	Height            uint16
	CompressedBlock   uint32
}

// BGRA to RGBA
type BGRA struct {
	image.RGBA
}

func (p *BGRA) RGBAAt(x, y int) color.RGBA {
	c := p.RGBA.RGBAAt(x, y)
	return color.RGBA{R: c.B, G: c.G, B: c.R, A: c.A}
}

func (p *BGRA) At(x, y int) color.Color {
	return p.RGBAAt(x, y)
}

func (p *BGRA) SubImage(r image.Rectangle) image.Image {
	c := p.RGBA.SubImage(r).(*image.RGBA)
	return &BGRA{*c}
}

type GA8 struct {
	Pix    []uint8
	Stride int
	Rect   image.Rectangle
}

func (p *GA8) ColorModel() color.Model { return color.RGBAModel }

func (p *GA8) Bounds() image.Rectangle { return p.Rect }

func (p *GA8) At(x, y int) color.Color {
	return p.GA8At(x, y)
}

func (p *GA8) GA8At(x, y int) color.RGBA {
	if !(image.Point{x, y}.In(p.Rect)) {
		return color.RGBA{}
	}
	i := p.PixOffset(x, y)
	if i+2 > len(p.Pix) {
		return color.RGBA{}
	}
	s := p.Pix[i : i+2 : i+2] // Small cap improves performance, see https://golang.org/issue/27857
	return color.RGBA{s[0], s[0], s[0], s[1]}
}

func (p *GA8) PixOffset(x, y int) int {
	return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*2
}

func unsupportedJPEGCompression() error {
	return fmt.Errorf("unsupported JPEGLZFSE decode: JPEG color data with optional LZFSE alpha is not implemented")
}

func decodeImage(r io.Reader, ci csiHeader, conf *Config, rowBytesOverride int) (image.Image, error) {
	var out bytes.Buffer
	// Track Deepmap2 origin and pixel format so we can render without BGRA swap
	fromDeepmap2 := false
	var deepmap2PixFmt deepmapPixelFormat

	var elem csiBitmap
	if err := binary.Read(r, binary.LittleEndian, &elem); err != nil {
		return nil, fmt.Errorf("failed to read CSIBitmap: %s", err)
	}

	// log.WithFields(log.Fields{
	// 	"signature": string(elem.Signature[:]),
	// 	"flags":     elem.Flags.String(),
	// 	"encoding":  elem.Encoding,
	// 	"length":    elem.Length,
	// }).Info("Reading CSIElement")

	if elem.Flags.ChunksFollow() {
		for i := uint32(0); i < elem.Length; i++ {
			var chunk csiBitmapChunk
			if err := binary.Read(r, binary.LittleEndian, &chunk); err != nil {
				return nil, err
			}
			if chunk.Signature != [4]byte{'K', 'C', 'B', 'C'} {
				return nil, fmt.Errorf("invalid chunk signature: %s", chunk.Signature)
			}
			data := make([]byte, chunk.Length)
			if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
				return nil, err
			}
			switch elem.Encoding {
			case Uncompressed:
				out.Write(data)
			case RLE:
				out.Write(decodeRLE(data))
			case ZIP:
				// Try gzip first
				gr, err := gzip.NewReader(bytes.NewReader(data))
				if err == nil {
					if _, err := io.Copy(&out, gr); err != nil {
						return nil, fmt.Errorf("failed to decompress gzip: %v", err)
					}
				} else {
					// Try zlib
					zr, err := zlib.NewReader(bytes.NewReader(data))
					if err == nil {
						if _, err := io.Copy(&out, zr); err != nil {
							return nil, fmt.Errorf("failed to decompress zlib: %v", err)
						}
						zr.Close()
					} else {
						// Try raw deflate
						fr := flate.NewReader(bytes.NewReader(data))
						if _, err := io.Copy(&out, fr); err != nil {
							// Last resort - assume uncompressed
							out.Write(data)
						}
						fr.Close()
					}
				}
			case LZVN:
				dec := make([]byte, len(data)*4)
				if n := lzfse.DecodeLZVNBuffer(data, dec); n == 0 {
					return nil, fmt.Errorf("failed to decompress lzvn data")
				} else {
					out.Write(dec[:n])
				}
			case LZFSE:
				decompressed, err := comp.Decompress(data, comp.LZFSE)
				if err != nil {
					return nil, fmt.Errorf("failed to decompress LZFSE data: %v", err)
				}
				out.Write(decompressed)
			case JPEGLZFSE:
				return nil, unsupportedJPEGCompression()
			case HEVC:
				// HEVC/H.265 video data
				var hevcInfo csiHEVCData
				r := bytes.NewReader(data)
				if err := binary.Read(r, binary.LittleEndian, &hevcInfo); err != nil {
					return nil, fmt.Errorf("failed to read HEVC header: %v", err)
				}
				// Read the HEVC data
				hevcData := make([]byte, hevcInfo.HevcDataSize)
				if err := binary.Read(r, binary.LittleEndian, &hevcData); err != nil {
					return nil, fmt.Errorf("failed to read HEVC data: %v", err)
				}
				// Output raw HEVC data (would need HEVC decoder for actual image)
				out.Write(hevcData)
			case PaletteImage:
				// Magic byte detection for compression format
				if len(data) >= 4 {
					magic := data[0:4]
					if string(magic) == "lzvn" || (len(data) >= 4 && magic[0] == 0x68 && magic[1] == 0x01 && magic[2] == 0x00 && magic[3] == 0xf0) {
						// LZVN compressed palette image
						dec := make([]byte, len(data)*4)
						if n := lzfse.DecodeLZVNBuffer(data, dec); n == 0 {
							return nil, fmt.Errorf("failed to decompress PaletteImage LZVN data")
						} else {
							out.Write(dec[:n])
						}
					} else {
						// Raw palette image data
						out.Write(data)
					}
				} else {
					out.Write(data)
				}
			case ASTCImage:
				// ASTC texture compression
				var astcInfo csiASTCData
				r := bytes.NewReader(data)
				if err := binary.Read(r, binary.LittleEndian, &astcInfo); err != nil {
					return nil, fmt.Errorf("failed to read ASTC header: %v", err)
				}
				// Read the ASTC data
				astcData := make([]byte, astcInfo.AstcDataSize)
				if err := binary.Read(r, binary.LittleEndian, &astcData); err != nil {
					return nil, fmt.Errorf("failed to read ASTC data: %v", err)
				}
				if astcInfo.Version == 1 {
					// LZFSE compressed ASTC data
					decompressed, err := comp.Decompress(astcData, comp.LZFSE)
					if err != nil {
						return nil, fmt.Errorf("failed to decompress ASTC data: %v", err)
					}
					out.Write(decompressed)
				} else {
					// Raw ASTC data
					out.Write(astcData)
				}
			case DeepmapLZFSE:
				// Deepmap with LZFSE compression
				var deepmapInfo csiDeepmapData
				r := bytes.NewReader(data)
				if err := binary.Read(r, binary.LittleEndian, &deepmapInfo); err != nil {
					return nil, fmt.Errorf("failed to read Deepmap header: %v", err)
				}
				log.WithFields(log.Fields{
					"version":          deepmapInfo.Version,
					"pixel_format":     deepmapInfo.PixelFormat,
					"compressed_bytes": deepmapInfo.CompressedBytes,
				}).Debug("Reading Deepmap")
				// Read the deepmap structure header
				var dm deepmap
				if err := binary.Read(r, binary.LittleEndian, &dm); err != nil {
					return nil, fmt.Errorf("failed to read deepmap structure: %v", err)
				}
				if dm.Signature != [4]byte{'d', 'm', 'a', 'p'} {
					return nil, fmt.Errorf("invalid deepmap signature: %s", dm.Signature)
				}
				// Read the compressed data
				compressedData := make([]byte, dm.CompressedBlock)
				if err := binary.Read(r, binary.LittleEndian, &compressedData); err != nil {
					return nil, fmt.Errorf("failed to read Deepmap compressed data: %v", err)
				}
				log.WithFields(log.Fields{
					"signature":          string(dm.Signature[:]),
					"compression_method": dm.CompressionMethod,
					"scale":              dm.Scale,
					"pixel_format":       dm.PixelFormat,
					"compressed_block":   dm.CompressedBlock,
				}).Debug("Reading Deepmap")
				if isLZFSE, _ := magic.IsLZFSE(compressedData); isLZFSE {
					// Decompress the data
					decompressed, err := comp.Decompress(compressedData, comp.LZFSE)
					if err != nil {
						return nil, fmt.Errorf("failed to decompress DeepmapLZFSE data: %v", err)
					}
					out.Write(decompressed)
				} else {
					out.Write(compressedData)
				}
				// switch dm.CompressionMethod {
				// case ImageDeepmapCompressionDefault:
				// 	// Decompress the data
				// 	decompressed, err := comp.Decompress(compressedData, comp.LZFSE)
				// 	if err != nil {
				// 		return nil, fmt.Errorf("failed to decompress DeepmapLZFSE data: %v", err)
				// 	}
				// 	out.Write(decompressed)
				// case ImageDeepmapCompressionNone:
				// 	out.Write(compressedData)
				// default:
				// 	out.Write(compressedData)
				// 	// return nil, fmt.Errorf("unsupported deepmap compression method: %d", dm.CompressionMethod)
				// }
			case Deepmap2:
				// Handle Deepmap2 inside chunk stream
				rdm := bytes.NewReader(data)
				var cdm2 csiDeepmap2Data
				if err := binary.Read(rdm, binary.LittleEndian, &cdm2); err != nil {
					return nil, err
				}
				var dm2 deepmap2
				if err := binary.Read(rdm, binary.LittleEndian, &dm2); err != nil {
					return nil, err
				}
				if dm2.Signature != [4]byte{'d', 'm', 'p', '2'} {
					return nil, fmt.Errorf("invalid deepmap2 signature: %s", dm2.Signature)
				}
				// Override dimensions; keep CSI pixel format (authoritative for GA8 masks)
				ci.Width = uint32(dm2.Width)
				ci.Height = uint32(dm2.Height)
				fromDeepmap2 = true
				deepmap2PixFmt = dm2.PixelFormat
				// Gather compressed payload which may span multiple KCBC chunks
				need := int(dm2.CompressedBlock)
				compressed := make([]byte, need)
				readSoFar := 0
				if rdm.Len() > 0 {
					frag := make([]byte, rdm.Len())
					if _, err := io.ReadFull(rdm, frag); err == nil {
						toCopy := min(len(frag), need)
						copy(compressed[0:toCopy], frag[:toCopy])
						readSoFar += toCopy
					}
				}
				// If not enough, consume following KCBC chunks from the outer reader 'r'.
				// Cap to remaining chunks in the element to avoid reading past the end.
				extraChunks := uint32(0)
				maxExtra := elem.Length - 1 - i
				for readSoFar < int(dm2.CompressedBlock) && extraChunks < maxExtra {
					var next csiBitmapChunk
					if err := binary.Read(r, binary.LittleEndian, &next); err != nil {
						break
					}
					if next.Signature != [4]byte{'K', 'C', 'B', 'C'} {
						break
					}
					buf := make([]byte, next.Length)
					if err := binary.Read(r, binary.LittleEndian, &buf); err != nil {
						break
					}
					remain := int(dm2.CompressedBlock) - readSoFar
					toCopy := min(len(buf), remain)
					copy(compressed[readSoFar:readSoFar+toCopy], buf[:toCopy])
					readSoFar += toCopy
					extraChunks++
				}
				// Skip accounting for consumed chunks in the outer loop
				i += extraChunks
				compressed = compressed[:readSoFar]
				// Palette compression carries a palette before an LZFSE block; decode directly to image
				if dm2.CompressionMethod == ImageDeepmapCompressionPalette {
					magic := []byte("bvx2")
					pos := bytes.Index(compressed, magic)
					if pos > 0 {
						palette := compressed[:pos]
						indicesCompressed := compressed[pos:]
						decomp, err := comp.Decompress(indicesCompressed, comp.LZFSE)
						if err == nil {
							// Compose [palette][indices] and decode
							combo := append([]byte{}, append(palette, decomp...)...)
							if palImg, err := decodePalettedImage(combo, int(ci.Width), int(ci.Height)); err == nil {
								return palImg, nil
							}
						}
					}
				}
				switch cdm2.Encoding {
				case LZFSE:
					if isLZFSE, _ := magic.IsLZFSE(compressed); isLZFSE {
						decompressed, err := comp.Decompress(compressed, comp.LZFSE)
						if err != nil {
							return nil, fmt.Errorf("failed to decompress Deepmap2 LZFSE data: %v", err)
						}
						out.Write(decompressed)
					} else {
						// Some payloads are raw when marked LZFSE
						out.Write(compressed)
					}
				case ZIP:
					if isLZFSE, _ := magic.IsLZFSE(compressed); isLZFSE {
						decompressed, err := comp.Decompress(compressed, comp.LZFSE)
						if err != nil {
							return nil, fmt.Errorf("failed to decompress Deepmap2 LZFSE-as-ZIP data: %v", err)
						}
						out.Write(decompressed)
						break
					}
					if gr, err := gzip.NewReader(bytes.NewReader(compressed)); err == nil {
						if _, err := io.Copy(&out, gr); err != nil {
							return nil, fmt.Errorf("failed to decompress gzip data: %v", err)
						}
					} else if zr, err := zlib.NewReader(bytes.NewReader(compressed)); err == nil {
						if _, err := io.Copy(&out, zr); err != nil {
							return nil, fmt.Errorf("failed to decompress zlib data: %v", err)
						}
						zr.Close()
					} else {
						fr := flate.NewReader(bytes.NewReader(compressed))
						if _, err := io.Copy(&out, fr); err != nil {
							// Assume raw on failure
							out.Write(compressed)
						}
						fr.Close()
					}
				case Deepmap2:
					// Nested Deepmap2 often uses LZFSE after a small header
					if len(data) > 16 {
						decompressed, err := comp.Decompress(data[16:], comp.LZFSE)
						if err != nil {
							return nil, fmt.Errorf("failed to decompress nested Deepmap2: %v", err)
						}
						out.Write(decompressed)
					} else {
						out.Write(compressed)
					}
				default:
					return nil, fmt.Errorf("unsupported deepmap2 encoding: %s", cdm2.Encoding)
				}
			default:
				return nil, fmt.Errorf("unknown encoding: %s (value: %d)", elem.Encoding, elem.Encoding)
			}
		}
	} else {
		data := make([]byte, elem.Length)
		if err := binary.Read(r, binary.LittleEndian, &data); err != nil {
			return nil, err
		}
		switch elem.Encoding {
		case Uncompressed:
			out.Write(data)
		case RLE:
			out.Write(decodeRLE(data))
		case ZIP:
			// Try gzip first
			gr, err := gzip.NewReader(bytes.NewReader(data))
			if err == nil {
				if _, err := io.Copy(&out, gr); err != nil {
					return nil, fmt.Errorf("failed to decompress gzip: %v", err)
				}
			} else {
				// Try zlib
				zr, err := zlib.NewReader(bytes.NewReader(data))
				if err == nil {
					if _, err := io.Copy(&out, zr); err != nil {
						return nil, fmt.Errorf("failed to decompress zlib: %v", err)
					}
					zr.Close()
				} else {
					// Try raw deflate
					fr := flate.NewReader(bytes.NewReader(data))
					if _, err := io.Copy(&out, fr); err != nil {
						// Last resort - assume uncompressed
						out.Write(data)
					}
					fr.Close()
				}
			}
		case LZVN:
			dec := make([]byte, len(data)*4)
			if n := lzfse.DecodeLZVNBuffer(data, dec); n == 0 {
				return nil, fmt.Errorf("failed to decompress lzvn data")
			} else {
				out.Write(dec[:n])
			}
		case LZFSE:
			decompressed, err := comp.Decompress(data, comp.LZFSE)
			if err != nil {
				return nil, fmt.Errorf("failed to decompress LZFSE data: %v", err)
			}
			out.Write(decompressed)
		case JPEGLZFSE:
			return nil, unsupportedJPEGCompression()
		case HEVC:
			// HEVC/H.265 video data
			var hevcInfo csiHEVCData
			r := bytes.NewReader(data)
			if err := binary.Read(r, binary.LittleEndian, &hevcInfo); err != nil {
				return nil, fmt.Errorf("failed to read HEVC header: %v", err)
			}
			// Read the HEVC data
			hevcData := make([]byte, hevcInfo.HevcDataSize)
			if err := binary.Read(r, binary.LittleEndian, &hevcData); err != nil {
				return nil, fmt.Errorf("failed to read HEVC data: %v", err)
			}
			// Output raw HEVC data (would need HEVC decoder for actual image)
			out.Write(hevcData)
		case ASTCImage:
			// ASTC texture compression
			var astcInfo csiASTCData
			r := bytes.NewReader(data)
			if err := binary.Read(r, binary.LittleEndian, &astcInfo); err != nil {
				return nil, fmt.Errorf("failed to read ASTC header: %v", err)
			}
			// Read the ASTC data
			astcData := make([]byte, astcInfo.AstcDataSize)
			if err := binary.Read(r, binary.LittleEndian, &astcData); err != nil {
				return nil, fmt.Errorf("failed to read ASTC data: %v", err)
			}
			if astcInfo.Version == 1 {
				// LZFSE compressed ASTC data
				decompressed, err := comp.Decompress(astcData, comp.LZFSE)
				if err != nil {
					return nil, fmt.Errorf("failed to decompress ASTC data: %v", err)
				}
				out.Write(decompressed)
			} else {
				// Raw ASTC data
				out.Write(astcData)
			}
		case PaletteImage:
			if isLZFSE, _ := magic.IsLZFSE(data); isLZFSE {
				// Decompress the data
				decompressed, err := comp.Decompress(data, comp.LZFSE)
				if err != nil {
					return nil, fmt.Errorf("failed to decompress DeepmapLZFSE data: %v", err)
				}
				out.Write(decompressed)
			} else {
				// Raw palette image data
				out.Write(data)
			}
			// After decompression, the data should contain palette indices
			// We'll handle this in the pixel format switch below
		case DeepmapLZFSE:
			// Deepmap with LZFSE compression
			var deepmapInfo csiDeepmapData
			r := bytes.NewReader(data)
			if err := binary.Read(r, binary.LittleEndian, &deepmapInfo); err != nil {
				return nil, fmt.Errorf("failed to read Deepmap header: %v", err)
			}
			var dm deepmap
			if err := binary.Read(r, binary.LittleEndian, &dm); err != nil {
				return nil, err
			}
			if dm.Signature != [4]byte{'d', 'm', 'a', 'p'} {
				return nil, fmt.Errorf("invalid deepmap signature: %s", dm.Signature)
			}
			// Read the compressed data
			compressedData := make([]byte, dm.CompressedBlock)
			if err := binary.Read(r, binary.LittleEndian, &compressedData); err != nil {
				return nil, fmt.Errorf("failed to read Deepmap compressed data: %v", err)
			}
			switch dm.CompressionMethod {
			case ImageDeepmapCompressionLossless:
				if isLZFSE, _ := magic.IsLZFSE(compressedData); isLZFSE {
					// Decompress the data
					decompressed, err := comp.Decompress(compressedData, comp.LZFSE)
					if err != nil {
						return nil, fmt.Errorf("failed to decompress DeepmapLZFSE data: %v", err)
					}
					out.Write(decompressed)
				} else {
					return nil, fmt.Errorf("lzfse magic bytes not found in DeepmapLZFSE data")
				}
			case ImageDeepmapCompressionDefault:
				out.Write(decodeRLE(compressedData))
			case ImageDeepmapCompressionNone:
				out.Write(compressedData)
			case ImageDeepmapCompressionPalette:
				return nil, fmt.Errorf("deepmap palette compression not implemented")
			}
		case Deepmap2:
			dmr := bytes.NewReader(data)
			var cdm2 csiDeepmap2Data
			if err := binary.Read(dmr, binary.LittleEndian, &cdm2); err != nil {
				return nil, err
			}
			if conf != nil && conf.Verbose {
				log.WithFields(log.Fields{
					"version":  cdm2.Version,
					"encoding": cdm2.Encoding,
					"length":   cdm2.Length,
				}).Info("Reading Deepmap2 Data")
			}
			var dm2 deepmap2
			if err := binary.Read(dmr, binary.LittleEndian, &dm2); err != nil {
				return nil, err
			}
			if dm2.Signature != [4]byte{'d', 'm', 'p', '2'} {
				return nil, fmt.Errorf("invalid deepmap2 signature: %s", dm2.Signature)
			}
			if conf != nil && conf.Verbose {
				log.WithFields(log.Fields{
					"signature":          string(dm2.Signature[:]),
					"blob_version":       dm2.BlobVersion,
					"pixel_format":       dm2.PixelFormat,
					"compression_method": dm2.CompressionMethod,
					"width":              dm2.Width,
					"height":             dm2.Height,
					"scale":              dm2.Scale,
					"compressed_block":   dm2.CompressedBlock,
				}).Warn("Reading Deepmap2")
			}
			if dm2.BlobVersion != 1 {
				return nil, fmt.Errorf("unsupported deepmap2 blob version: %d", dm2.BlobVersion)
			}

			// Override dimensions; keep CSI pixel format (authoritative for GA8 masks)
			ci.Width = uint32(dm2.Width)
			ci.Height = uint32(dm2.Height)
			fromDeepmap2 = true
			deepmap2PixFmt = dm2.PixelFormat
			switch cdm2.Encoding {
			case LZFSE, ZIP:
				// Gather the full Deepmap2 compressed block, which may follow in KCBC chunks
				need := int(dm2.CompressedBlock)
				compressed := make([]byte, need)
				readSoFar := 0
				if dmr.Len() > 0 {
					frag := make([]byte, dmr.Len())
					if _, err := io.ReadFull(dmr, frag); err == nil {
						toCopy := min(len(frag), need)
						copy(compressed[0:], frag[:toCopy])
						readSoFar += toCopy
					}
				}
				// In the non-chunked path, the blob may be smaller than
				// CompressedBlock claims — proceed with what we have.
				compressed = compressed[:readSoFar]

				// If palette compression, split palette and indices and decode paletted directly
				if dm2.CompressionMethod == ImageDeepmapCompressionPalette {
					// Look for LZFSE block header magic commonly seen as 'bvx2'
					pos := bytes.Index(compressed, []byte("bvx2"))
					if pos > 0 {
						palette := compressed[:pos]
						indicesCompressed := compressed[pos:]
						// Some files mark ZIP but actually contain LZFSE
						decomp, err := comp.Decompress(indicesCompressed, comp.LZFSE)
						if err == nil {
							combo := append([]byte{}, append(palette, decomp...)...)
							if palImg, err := decodePalettedImage(combo, int(ci.Width), int(ci.Height)); err == nil {
								return palImg, nil
							}
						}
					}
				}

				// Otherwise, decompress the full block
				if cdm2.Encoding == ZIP {
					if isLZFSE, _ := magic.IsLZFSE(compressed); isLZFSE {
						decompressed, err := comp.Decompress(compressed, comp.LZFSE)
						if err != nil {
							return nil, fmt.Errorf("failed to decompress Deepmap2 LZFSE data: %v", err)
						}
						out.Write(decompressed)
					} else if gr, err := gzip.NewReader(bytes.NewReader(compressed)); err == nil {
						if _, err := io.Copy(&out, gr); err != nil {
							return nil, fmt.Errorf("failed to decompress gzip data: %v", err)
						}
					} else if zr, err := zlib.NewReader(bytes.NewReader(compressed)); err == nil {
						if _, err := io.Copy(&out, zr); err != nil {
							return nil, fmt.Errorf("failed to decompress zlib data: %v", err)
						}
						zr.Close()
					} else {
						fr := flate.NewReader(bytes.NewReader(compressed))
						if _, err := io.Copy(&out, fr); err != nil {
							out.Write(compressed)
						}
						fr.Close()
					}
				} else {
					if isLZFSE, _ := magic.IsLZFSE(compressed); isLZFSE {
						decompressed, err := comp.Decompress(compressed, comp.LZFSE)
						if err != nil {
							return nil, fmt.Errorf("failed to decompress LZFSE data: %v", err)
						}
						out.Write(decompressed)
					} else {
						out.Write(compressed)
					}
				}
			case Deepmap2:
				// Handle nested Deepmap2 encoding
				if dmr.Len() > 16 {
					payload := make([]byte, dmr.Len())
					if _, err := io.ReadFull(dmr, payload); err == nil {
						decompressed, err := comp.Decompress(payload[16:], comp.LZFSE)
						if err != nil {
							return nil, fmt.Errorf("failed to decompress nested Deepmap2: %v", err)
						}
						out.Write(decompressed)
					}
				}
			default:
				return nil, fmt.Errorf("unsupported deepmap2 encoding: %s", cdm2.Encoding)
			}
		default:
			return nil, fmt.Errorf("unknown encoding: %s (value: %d)", elem.Encoding, elem.Encoding)
		}
	}

	// Check for invalid image dimensions
	if ci.Width == 0 || ci.Height == 0 {
		return nil, fmt.Errorf("invalid image dimensions: %dx%d", ci.Width, ci.Height)
	}

	// Check if this was a PaletteImage compression type
	// If so, decode as a paletted image regardless of pixel format
	if elem.Encoding == PaletteImage {
		// Try to decode as a paletted image
		if palImg, err := decodePalettedImage(out.Bytes(), int(ci.Width), int(ci.Height)); err == nil {
			return palImg, nil
		} else {
			// If palette decoding fails, log and try normal pixel format handling as fallback
			log.Debugf("Failed to decode as PaletteImage for %s: %v, falling back to pixel format", string(bytes.Trim(ci.Metadata.Name[:], "\x00")), err)
		}
	}

	format := string(ci.PixelFormat[:])
	switch format {
	case PixFmtARGB, PixFmtARGB16:
		// Special handling for IconImage layout which often uses channel-separated ARGB format (AAAA RRRR GGGG BBBB)
		if ci.Metadata.Layout == IconImage {
			pixelCount := int(ci.Width * ci.Height)
			// Prefer exact-sized channel data, but allow >= to tolerate minor padding
			if out.Len() >= pixelCount*4 {
				if img, err := decodeAppIconARGB(out.Bytes(), int(ci.Width), int(ci.Height)); err == nil {
					return img, nil
				}
			}
		}

		var offset int
		bytesPerPixel := 4 // Default for ARGB (8-bit per channel)
		if format == PixFmtARGB16 {
			if !fromDeepmap2 {
				return nil, fmt.Errorf("unsupported pixel format: %s outside Deepmap2", format)
			}
			bytesPerPixel = 8 // 16-bit per channel (2 bytes per channel × 4 channels)
		}

		expectedSize := int(ci.Width * ci.Height * uint32(bytesPerPixel))
		actualSize := out.Len()

		if actualSize < expectedSize {
			// Dump raw bytes for debugging when verbose
			if conf != nil && conf.Verbose && conf.Export && conf.Output != "" {
				name := strings.Trim(string(bytes.Trim(ci.Metadata.Name[:], "\x00")), " ")
				if name == "" {
					name = fmt.Sprintf("asset_%dx%d_%s", ci.Width, ci.Height, format)
				}
				errPath := filepath.Join(conf.Output, fmt.Sprintf("%s.error", name))
				_ = os.WriteFile(errPath, out.Bytes(), 0644)
			}
			return nil, fmt.Errorf("insufficient image data: got %d bytes, expected %d bytes for %dx%d %s", actualSize, expectedSize, ci.Width, ci.Height, format)
		}

		if v := actualSize - expectedSize; v != 0 {
			offset = v / int(ci.Height*uint32(bytesPerPixel))
		}
		rect := image.Rectangle{
			Min: image.Point{0, 0},
			Max: image.Point{
				X: int(ci.Width),
				Y: int(ci.Height),
			},
		}
		// rgba := image.NewRGBA(rect)
		// rgba.Pix = out.Bytes()
		// rgba.Stride = (rect.Dx() + offset) * 4
		// return rgba, nil
		// Deepmap2 emits RGBA ordering; do not apply BGRA swap, and downconvert 16-bit to 8-bit
		if fromDeepmap2 {
			if format == PixFmtARGB16 || deepmap2PixFmt == ImageDeepmapPixelFormatRGBA16 {
				// downconvert RGBA16 (LE) to RGBA8
				in := out.Bytes()
				px := rect.Dx() * rect.Dy()
				dst := make([]byte, px*4)
				for i := range px {
					j := i * 8
					// take high byte of each 16-bit LE component
					r := in[j+1]
					g := in[j+3]
					b := in[j+5]
					a := in[j+7]
					k := i * 4
					dst[k+0] = r
					dst[k+1] = g
					dst[k+2] = b
					dst[k+3] = a
				}
				stride := rect.Dx() * 4
				img := &image.RGBA{Pix: dst, Stride: stride, Rect: rect}
				return img, nil
			}
			// RGBA8
			stride := rect.Dx() * 4
			if rowBytesOverride > 0 {
				stride = rowBytesOverride
			}
			img := &image.RGBA{Pix: out.Bytes(), Stride: stride, Rect: rect}
			return img, nil
		}

		// Default path: treat as BGRA in memory and swap when reading
		stride := (rect.Dx() + offset) * bytesPerPixel
		if rowBytesOverride > 0 {
			stride = rowBytesOverride
		}
		bgra := &BGRA{image.RGBA{
			Pix:    out.Bytes(),
			Stride: stride,
			Rect:   rect,
		}}
		return bgra, nil
	case PixFmtGray, PixFmtGray16:
		if format == PixFmtGray16 {
			return nil, fmt.Errorf("unsupported pixel format: %s", format)
		}
		var offset int
		bytesPerPixel := 2 // GA8: gray + alpha (1 byte each)

		expectedSize := int(ci.Width * ci.Height * uint32(bytesPerPixel))
		actualSize := out.Len()

		if actualSize < expectedSize {
			// Not enough data for the image
			return nil, fmt.Errorf("insufficient image data: got %d bytes, expected %d bytes for %dx%d %s", actualSize, expectedSize, ci.Width, ci.Height, format)
		}

		if v := actualSize - expectedSize; v != 0 {
			offset = v / int(ci.Height*uint32(bytesPerPixel))
		}

		rect := image.Rectangle{
			Min: image.Point{0, 0},
			Max: image.Point{
				X: int(ci.Width),
				Y: int(ci.Height),
			},
		}
		stride := (rect.Dx() + offset) * bytesPerPixel
		if rowBytesOverride > 0 {
			stride = rowBytesOverride
		}
		bgra := &GA8{
			Pix:    out.Bytes(),
			Stride: stride,
			Rect:   rect,
		}
		return bgra, nil
	default:
		return nil, fmt.Errorf("unknown pixel format: %s", format)
	}
}

// decodePalettedImage creates a paletted image from palette data and indices
// The format is typically: [palette colors (256 * 4 bytes RGBA)][pixel indices]
// cafef00dMagic identifies CoreUI palette rendition data.
const cafef00dMagic = 0xcafef00d

func decodePalettedImage(data []byte, width, height int) (image.Image, error) {
	pixelCount := width * height

	// CoreUI palette format: [uint32 magic 0xcafef00d][uint32 version]
	// [uint16 palette_count][count*4 RGBA entries][width*height indices]
	if len(data) >= 10 {
		magic := binary.LittleEndian.Uint32(data[0:4])
		if magic == cafef00dMagic {
			count := int(binary.LittleEndian.Uint16(data[8:10]))
			hdr := 10
			palBytes := count * 4
			if len(data) < hdr+palBytes+pixelCount {
				return nil, fmt.Errorf("cafef00d palette data too short: got %d, need %d (hdr=%d palette=%d indices=%d)",
					len(data), hdr+palBytes+pixelCount, hdr, palBytes, pixelCount)
			}
			palette := make(color.Palette, count)
			for i := range count {
				off := hdr + i*4
				palette[i] = color.RGBA{
					R: data[off],
					G: data[off+1],
					B: data[off+2],
					A: data[off+3],
				}
			}
			indices := data[hdr+palBytes:]
			rect := image.Rect(0, 0, width, height)
			img := image.NewPaletted(rect, palette)
			copy(img.Pix, indices[:pixelCount])
			return img, nil
		}
	}

	// Fallback: fixed 256-entry palette + indices
	const fixedPaletteSize = 256
	fixedPalBytes := fixedPaletteSize * 4
	if len(data) >= fixedPalBytes+pixelCount {
		palette := make(color.Palette, fixedPaletteSize)
		for i := range fixedPaletteSize {
			off := i * 4
			palette[i] = color.RGBA{
				R: data[off],
				G: data[off+1],
				B: data[off+2],
				A: data[off+3],
			}
		}
		rect := image.Rect(0, 0, width, height)
		img := image.NewPaletted(rect, palette)
		copy(img.Pix, data[fixedPalBytes:fixedPalBytes+pixelCount])
		return img, nil
	}

	// Fallback: indices only (grayscale palette)
	if len(data) >= pixelCount {
		palette := make(color.Palette, 256)
		for i := range 256 {
			palette[i] = color.RGBA{uint8(i), uint8(i), uint8(i), 255}
		}
		rect := image.Rect(0, 0, width, height)
		img := image.NewPaletted(rect, palette)
		copy(img.Pix, data[:pixelCount])
		return img, nil
	}

	return nil, fmt.Errorf("insufficient data for paletted image: got %d bytes, need %d (width=%d height=%d)",
		len(data), pixelCount, width, height)
}

// decodeAppIconARGB decodes AppIcon ARGB format where channels are separated
// Instead of interleaved ARGBARGBARGB, the data is stored as AAARRRGGGBBB
func decodeAppIconARGB(data []byte, width, height int) (image.Image, error) {
	pixelCount := width * height
	expectedSize := pixelCount * 4 // 4 bytes per pixel (ARGB)

	if len(data) < expectedSize {
		return nil, fmt.Errorf("insufficient data for AppIcon ARGB: got %d bytes, expected %d", len(data), expectedSize)
	}

	rect := image.Rectangle{
		Min: image.Point{0, 0},
		Max: image.Point{X: width, Y: height},
	}

	img := image.NewRGBA(rect)

	// Extract the separate channels
	// The format is: all alpha values, then all red, then all green, then all blue
	alphaChannel := data[0:pixelCount]
	redChannel := data[pixelCount : pixelCount*2]
	greenChannel := data[pixelCount*2 : pixelCount*3]
	blueChannel := data[pixelCount*3 : pixelCount*4]

	// Reconstruct interleaved RGBA pixels
	for i := range pixelCount {
		pixelIndex := i * 4
		img.Pix[pixelIndex+0] = redChannel[i]   // R
		img.Pix[pixelIndex+1] = greenChannel[i] // G
		img.Pix[pixelIndex+2] = blueChannel[i]  // B
		img.Pix[pixelIndex+3] = alphaChannel[i] // A
	}

	return img, nil
}
