import CoreGraphics
// SPDX-License-Identifier: MIT
import Foundation
import UIKit

/// Image rasterization for ESC/POS printers
/// Optimized version using Bayer dithering to match Android implementation
public class ImageRaster {

  // Paper width standards in pixels (203 DPI)
  private static let WIDTH_58MM = 384
  private static let WIDTH_80MM = 576
  private static let WIDTH_112MM = 832

  // Max band height - 50 lines like Android to avoid buffer overflow
  private static let MAX_BAND_HEIGHT = 50

  /**
   * Process image file to ESC/POS raster commands
   * Main entry point for image printing - now uses file path like Android
   * @param imagePath Path to image file
   * @param widthPx Target width in pixels
   * @param isCutPaper Whether to cut paper after printing
   * @return ESC/POS commands as byte array
   */
  public static func processImageFile(
    imagePath: String,
    widthPx: Int = 384,  // WIDTH_58MM
    paperWidthMm: Int? = nil,  // Optional paper width for margin calc
    isCutPaper: Bool = false,
    marginMm: Double = 0.0,
    align: Int = 0  // 0=left, 1=center, 2=right
  ) -> Data? {
    print("[Native:iOS] ImageRaster.processImageFile START: path=\(imagePath), width=\(widthPx)px, paper=\(paperWidthMm ?? 0)mm")

    // Load image from file
    guard let image = UIImage(contentsOfFile: imagePath) else {
      print("[Native:iOS] ImageRaster.processImageFile ERROR: Cannot load image from \(imagePath)")
      return nil
    }

    print("[Native:iOS] ImageRaster.processImageFile LOADED: \(image.size.width)x\(image.size.height)")

    // Use widthPx directly as target width
    let targetWidth = widthPx

    // Scale image to target width
    guard let scaledImage = scaleImage(image, targetWidth: targetWidth) else {
      print("[Native:iOS] ImageRaster.processImageFile ERROR: Failed to scale image")
      return nil
    }

    print("[Native:iOS] ImageRaster.processImageFile SCALED: \(scaledImage.size.width)x\(scaledImage.size.height)")

    // Generate ESC/POS raster commands with Bayer dithering
    let rasterData = toSimpleESCPOSRaster(scaledImage, paperWidthMm: paperWidthMm, isCutPaper: isCutPaper, marginMm: marginMm, align: align)

    print("[Native:iOS] ImageRaster.processImageFile SUCCESS: \(rasterData.count) bytes generated")
    return rasterData
  }

  /**
   * Determine target width based on paper size
   */
  private static func determineTargetWidth(_ requestedWidth: Int) -> Int {
    if requestedWidth <= WIDTH_58MM {
      return WIDTH_58MM   // 58mm paper
    } else if requestedWidth <= WIDTH_80MM {
      return WIDTH_80MM   // 80mm paper
    } else {
      return WIDTH_112MM  // 112mm paper
    }
  }

  /**
   * Scale image to target width maintaining aspect ratio
   */
  private static func scaleImage(_ image: UIImage, targetWidth: Int) -> UIImage? {
    let scale = CGFloat(targetWidth) / image.size.width
    let targetHeight = image.size.height * scale

    let size = CGSize(width: CGFloat(targetWidth), height: targetHeight)
    UIGraphicsBeginImageContextWithOptions(size, false, 1.0)

    image.draw(in: CGRect(origin: .zero, size: size))
    let scaledImage = UIGraphicsGetImageFromCurrentImageContext()

    UIGraphicsEndImageContext()

    return scaledImage
  }

  /**
   * Convert image to ESC/POS raster commands using Bayer dithering
   * Matches Android implementation for consistency
   */
  private static func toSimpleESCPOSRaster(
    _ image: UIImage,
    paperWidthMm: Int?,
    isCutPaper: Bool,
    marginMm: Double,
    align: Int
  ) -> Data {
    guard let cgImage = image.cgImage else { return Data() }

    let width = cgImage.width
    let height = cgImage.height
    let bytesPerRow = (width + 7) / 8  // Round up to nearest byte

    var commands = Data()

    // Initialize printer
    commands.append(contentsOf: [0x1B, 0x40])  // ESC @ - Reset printer
    print("[Native:iOS] ESC/POS: Initialize printer")

    // Set print area and margin
    // Use paperWidthMm if provided, otherwise no margin commands needed
    if let paperWidth = paperWidthMm {
      let DOTS_PER_MM = 8

      // Set print area width (GS W) with margins subtracted
      let printAreaMm = Double(paperWidth) - (marginMm * 2)
      let printAreaDots = Int(printAreaMm * Double(DOTS_PER_MM))
      let wL = UInt8(printAreaDots & 0xFF)
      let wH = UInt8((printAreaDots >> 8) & 0xFF)
      commands.append(contentsOf: [0x1D, 0x57, wL, wH])  // GS W - Set print area width
      print("[Native:iOS] ESC/POS: Set print area \(printAreaMm)mm (\(printAreaDots) dots) with \(marginMm)mm margins")

      // Set left margin (GS L) to offset print area from left edge
      if marginMm > 0 {
        let marginDots = Int(marginMm * Double(DOTS_PER_MM))
        let nL = UInt8(marginDots & 0xFF)
        let nH = UInt8((marginDots >> 8) & 0xFF)
        commands.append(contentsOf: [0x1D, 0x4C, nL, nH])  // GS L - Set left margin
        print("[Native:iOS] ESC/POS: Set left margin \(marginMm)mm (\(marginDots) dots)")
      }
    }

    // Set alignment
    let alignByte = UInt8(align)  // 0=left, 1=center, 2=right
    commands.append(contentsOf: [0x1B, 0x61, alignByte])  // ESC a n
    let alignStr = align == 0 ? "left" : (align == 1 ? "center" : "right")
    print("[Native:iOS] ESC/POS: Set alignment to \(alignStr)")

    // Bayer 8x8 dithering matrix (same as Android)
    let bayer8: [[Int]] = [
      [0, 48, 12, 60, 3, 51, 15, 63],
      [32, 16, 44, 28, 35, 19, 47, 31],
      [8, 56, 4, 52, 11, 59, 7, 55],
      [40, 24, 36, 20, 43, 27, 39, 23],
      [2, 50, 14, 62, 1, 49, 13, 61],
      [34, 18, 46, 30, 33, 17, 45, 29],
      [10, 58, 6, 54, 9, 57, 5, 53],
      [42, 26, 38, 22, 41, 25, 37, 21]
    ]
    let gamma = 1.25  // Slight gamma to darken UI strokes (same as Android)
    print("[Native:iOS] DITHER: ordered 8x8, gamma=\(gamma)")

    // Get image pixel data
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue

    guard let context = CGContext(
      data: nil,
      width: width,
      height: height,
      bitsPerComponent: 8,
      bytesPerRow: width * 4,
      space: colorSpace,
      bitmapInfo: bitmapInfo
    ) else { return Data() }

    context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))

    guard let pixelBuffer = context.data else { return Data() }
    let pixels = pixelBuffer.bindMemory(to: UInt8.self, capacity: width * height * 4)

    // Process image in bands (max 50 lines per band like Android)
    var y = 0
    while y < height {
      let bandHeight = min(MAX_BAND_HEIGHT, height - y)

      // Send raster command
      commands.append(contentsOf: [0x1D, 0x76, 0x30, 0x00])  // GS v 0 0

      // Specify dimensions
      commands.append(UInt8(bytesPerRow & 0xFF))          // Width low byte
      commands.append(UInt8((bytesPerRow >> 8) & 0xFF))  // Width high byte
      commands.append(UInt8(bandHeight & 0xFF))           // Height low byte
      commands.append(UInt8((bandHeight >> 8) & 0xFF))   // Height high byte

      if y == 0 {
        print("[Native:iOS] GS v 0 0: width=\(bytesPerRow) bytes (\(width)px), height=\(bandHeight) lines")
      }

      // Convert pixels to printer format with Bayer dithering
      for row in 0..<bandHeight {
        for byteX in 0..<bytesPerRow {
          var byte: UInt8 = 0

          // Pack 8 pixels into 1 byte
          for bit in 0..<8 {
            let x = byteX * 8 + bit
            if x < width {
              let pixelIndex = ((y + row) * width + x) * 4

              // Extract RGB values (handling premultiplied alpha)
              let r = Int(pixels[pixelIndex])
              let g = Int(pixels[pixelIndex + 1])
              let b = Int(pixels[pixelIndex + 2])

              // Apply Bayer dithering with gamma correction (same as Android)
              let gray = (r + g + b) / 3
              let normalized = Double(gray) / 255.0
              let corrected = pow(normalized, gamma)
              let thresholdNorm = (Double(bayer8[(y + row) % 8][x % 8]) + 0.5) / 64.0
              let isDark = corrected < thresholdNorm

              if isDark {
                byte |= (1 << (7 - bit))
              }
            }
          }

          commands.append(byte)
        }
      }

      y += bandHeight
    }

    // Finalize printing - Do NOT feed/cut here to avoid double cut
    // Let upper layer (JS) decide feed/cut policy for mixed-content documents

    return commands
  }
}