const ROWS = 6
const COLS = 22

type Color = "red" | "orange" | "yellow" | "green" | "blue" | "purple" | "white" | "black"
type Align = "left" | "center" | "right"
type Side = "top" | "bottom" | "left" | "right"
type Overflow = "ellipsis" | "truncate" | "squeeze" | "error"

const colorToCode: Record<Color, number> = {
  red: 63,
  orange: 64,
  yellow: 65,
  green: 66,
  blue: 67,
  purple: 68,
  white: 69,
  black: 70,
}

const charToCode: Record<string, number> = {
  " ": 0,
  A: 1, B: 2, C: 3, D: 4, E: 5, F: 6, G: 7, H: 8, I: 9,
  J: 10, K: 11, L: 12, M: 13, N: 14, O: 15, P: 16, Q: 17,
  R: 18, S: 19, T: 20, U: 21, V: 22, W: 23, X: 24, Y: 25, Z: 26,
  "1": 27, "2": 28, "3": 29, "4": 30, "5": 31, "6": 32, "7": 33, "8": 34, "9": 35, "0": 36,
  "!": 37, "@": 38, "#": 39, "$": 40, "(": 41, ")": 42, "-": 44, "+": 46,
  "&": 47, "=": 48, ";": 49, ":": 50, "'": 52, '"': 53, "%": 54, ",": 55,
  ".": 56, "/": 59, "?": 60, "°": 62,
}

interface FillCmd {
  cmd: "fill"
  color: Color
}

interface RectCmd {
  cmd: "rect"
  x: number
  y: number
  w: number
  h: number
  color: Color
}

interface CircleCmd {
  cmd: "circle"
  cx: number
  cy: number
  r: number
  color: Color
}

interface LineCmd {
  cmd: "line"
  x1: number
  y1: number
  x2: number
  y2: number
  color: Color
}

interface TextCmd {
  cmd: "text"
  content: string
  row: number
  align?: Align
  startCol?: number
  endCol?: number
}

interface TextBlockCmd {
  cmd: "text_block"
  content: string
  startRow?: number
  endRow?: number
  startCol?: number
  endCol?: number
  align?: Align
  overflow?: Overflow
}

interface BorderCmd {
  cmd: "border"
  color: Color
  sides?: Side[]
}

interface GradientCmd {
  cmd: "gradient"
  direction: "horizontal" | "vertical"
  colors: Color[]
}

export type DrawCommand =
  | FillCmd
  | RectCmd
  | CircleCmd
  | LineCmd
  | TextCmd
  | TextBlockCmd
  | BorderCmd
  | GradientCmd

const createGrid = (): number[][] => {
  return Array.from({ length: ROWS }, () => Array(COLS).fill(0))
}

const setPixel = (grid: number[][], x: number, y: number, code: number) => {
  if (x >= 0 && x < COLS && y >= 0 && y < ROWS) {
    grid[y]![x] = code
  }
}

const fill = (grid: number[][], color: Color) => {
  const code = colorToCode[color]
  for (let y = 0; y < ROWS; y++) {
    for (let x = 0; x < COLS; x++) {
      grid[y]![x] = code
    }
  }
}

const rect = (grid: number[][], x: number, y: number, w: number, h: number, color: Color) => {
  const code = colorToCode[color]
  for (let dy = 0; dy < h; dy++) {
    for (let dx = 0; dx < w; dx++) {
      setPixel(grid, x + dx, y + dy, code)
    }
  }
}

const circle = (grid: number[][], cx: number, cy: number, r: number, color: Color) => {
  const code = colorToCode[color]
  for (let y = 0; y < ROWS; y++) {
    for (let x = 0; x < COLS; x++) {
      const dx = x - cx
      const dy = y - cy
      if (dx * dx + dy * dy <= r * r) {
        grid[y]![x] = code
      }
    }
  }
}

const line = (grid: number[][], x1: number, y1: number, x2: number, y2: number, color: Color) => {
  const code = colorToCode[color]
  const dx = Math.abs(x2 - x1)
  const dy = Math.abs(y2 - y1)
  const sx = x1 < x2 ? 1 : -1
  const sy = y1 < y2 ? 1 : -1
  let err = dx - dy

  let x = x1
  let y = y1

  while (true) {
    setPixel(grid, x, y, code)
    if (x === x2 && y === y2) break
    const e2 = 2 * err
    if (e2 > -dy) {
      err -= dy
      x += sx
    }
    if (e2 < dx) {
      err += dx
      y += sy
    }
  }
}

const text = (
  grid: number[][],
  content: string,
  row: number,
  startCol = 0,
  endCol = COLS - 1,
  align: Align = "center"
) => {
  const availableWidth = endCol - startCol + 1
  const textLen = Math.min(content.length, availableWidth)
  const truncatedContent = content.slice(0, textLen)

  let startX: number
  if (align === "left") {
    startX = startCol
  } else if (align === "right") {
    startX = endCol - textLen + 1
  } else {
    // center
    startX = startCol + Math.floor((availableWidth - textLen) / 2)
  }

  for (let i = 0; i < truncatedContent.length; i++) {
    const char = truncatedContent[i]!.toUpperCase()
    const code = charToCode[char] ?? 0
    setPixel(grid, startX + i, row, code)
  }
}

const wrapText = (content: string, maxWidth: number): string[] => {
  const words = content.split(" ")
  const lines: string[] = []
  let currentLine = ""

  for (const word of words) {
    if (word.length > maxWidth) {
      if (currentLine) {
        lines.push(currentLine.trim())
        currentLine = ""
      }
      let remaining = word
      while (remaining.length > maxWidth) {
        lines.push(remaining.slice(0, maxWidth - 1) + "-")
        remaining = remaining.slice(maxWidth - 1)
      }
      currentLine = remaining
    } else if ((currentLine + " " + word).trim().length <= maxWidth) {
      currentLine = (currentLine + " " + word).trim()
    } else {
      if (currentLine) {
        lines.push(currentLine)
      }
      currentLine = word
    }
  }

  if (currentLine) {
    lines.push(currentLine)
  }

  return lines
}

const textBlock = (
  grid: number[][],
  content: string,
  startRow = 0,
  endRow = ROWS - 1,
  startCol = 0,
  endCol = COLS - 1,
  align: Align = "center",
  overflow: Overflow = "ellipsis"
) => {
  const availableWidth = endCol - startCol + 1
  const availableRows = endRow - startRow + 1
  let lines = wrapText(content, availableWidth)

  if (lines.length > availableRows) {
    if (overflow === "ellipsis") {
      lines = lines.slice(0, availableRows)
      const lastLine = lines[lines.length - 1]!
      if (lastLine.length > availableWidth - 3) {
        lines[lines.length - 1] = lastLine.slice(0, availableWidth - 3) + "..."
      } else {
        lines[lines.length - 1] = lastLine + "..."
      }
    } else if (overflow === "truncate") {
      lines = lines.slice(0, availableRows)
    } else if (overflow === "squeeze") {
      // Try expanding bounds by 1 on each side
      const newStartCol = Math.max(0, startCol - 1)
      const newEndCol = Math.min(COLS - 1, endCol + 1)
      if (newStartCol < startCol || newEndCol > endCol) {
        return textBlock(grid, content, startRow, endRow, newStartCol, newEndCol, align, overflow)
      }
      lines = lines.slice(0, availableRows)
    } else if (overflow === "error") {
      throw new Error(`Text overflow: ${lines.length} lines needed, only ${availableRows} available`)
    }
  }

  for (let i = 0; i < lines.length; i++) {
    const row = startRow + i
    if (row <= endRow) {
      text(grid, lines[i]!, row, startCol, endCol, align)
    }
  }
}

const border = (grid: number[][], color: Color, sides?: Side[]) => {
  const code = colorToCode[color]
  const allSides = sides ?? ["top", "bottom", "left", "right"]

  if (allSides.includes("top")) {
    for (let x = 0; x < COLS; x++) {
      grid[0]![x] = code
    }
  }

  if (allSides.includes("bottom")) {
    for (let x = 0; x < COLS; x++) {
      grid[ROWS - 1]![x] = code
    }
  }

  if (allSides.includes("left")) {
    for (let y = 0; y < ROWS; y++) {
      grid[y]![0] = code
    }
  }

  if (allSides.includes("right")) {
    for (let y = 0; y < ROWS; y++) {
      grid[y]![COLS - 1] = code
    }
  }
}

const gradient = (grid: number[][], direction: "horizontal" | "vertical", colors: Color[]) => {
  const codes = colors.map(c => colorToCode[c])

  if (direction === "horizontal") {
    for (let x = 0; x < COLS; x++) {
      const t = x / (COLS - 1)
      const idx = Math.min(Math.floor(t * codes.length), codes.length - 1)
      const code = codes[idx]!
      for (let y = 0; y < ROWS; y++) {
        grid[y]![x] = code
      }
    }
  } else {
    for (let y = 0; y < ROWS; y++) {
      const t = y / (ROWS - 1)
      const idx = Math.min(Math.floor(t * codes.length), codes.length - 1)
      const code = codes[idx]!
      for (let x = 0; x < COLS; x++) {
        grid[y]![x] = code
      }
    }
  }
}

export const draw = (commands: DrawCommand[]): number[][] => {
  const grid = createGrid()

  for (const cmd of commands) {
    switch (cmd.cmd) {
      case "fill":
        fill(grid, cmd.color)
        break
      case "rect":
        rect(grid, cmd.x, cmd.y, cmd.w, cmd.h, cmd.color)
        break
      case "circle":
        circle(grid, cmd.cx, cmd.cy, cmd.r, cmd.color)
        break
      case "line":
        line(grid, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.color)
        break
      case "text":
        text(grid, cmd.content, cmd.row, cmd.startCol, cmd.endCol, cmd.align)
        break
      case "text_block":
        textBlock(grid, cmd.content, cmd.startRow, cmd.endRow, cmd.startCol, cmd.endCol, cmd.align, cmd.overflow)
        break
      case "border":
        border(grid, cmd.color, cmd.sides)
        break
      case "gradient":
        gradient(grid, cmd.direction, cmd.colors)
        break
    }
  }

  return grid
}
