local ffi_sdl = require("ffi_sdl")
local ffi_cairo = require("ffi_cairo")
local ffi = require("ffi")

local C = ffi_sdl.C
local cairo = ffi_cairo.C

local M = {}
M.buttons = {}

function M.add_button(btn)
  -- btn: { x, y, w, h, label, on_click }
  table.insert(M.buttons, btn)
  return btn
end

function M.layout_center(parent_w, parent_h, child_w, child_h)
  local x = math.floor((parent_w - child_w) / 2)
  local y = math.floor((parent_h - child_h) / 2)
  return x, y
end

local function rounded_rect(cr, x, y, w, h, r)
  if r <= 0 then
    cairo.cairo_rectangle(cr, x, y, w, h)
    return
  end
  local pi = math.pi
  cairo.cairo_move_to(cr, x + r, y)
  cairo.cairo_line_to(cr, x + w - r, y)
  cairo.cairo_arc(cr, x + w - r, y + r, r, -pi/2, 0)
  cairo.cairo_line_to(cr, x + w, y + h - r)
  cairo.cairo_arc(cr, x + w - r, y + h - r, r, 0, pi/2)
  cairo.cairo_line_to(cr, x + r, y + h)
  cairo.cairo_arc(cr, x + r, y + h - r, r, pi/2, pi)
  cairo.cairo_line_to(cr, x, y + r)
  cairo.cairo_arc(cr, x + r, y + r, r, pi, 3*pi/2)
  cairo.cairo_close_path(cr)
end

local function draw_button(cr, x, y, bw, bh, radius, label, state, shape)
  -- determine colors
  local fill_r, fill_g, fill_b = 0.2, 0.6, 0.9
  if state == "hover" then
    fill_r, fill_g, fill_b = 0.15, 0.55, 0.9
  elseif state == "active" then
    fill_r, fill_g, fill_b = 0.1, 0.5, 0.8
  end

  if state == nil then state = "normal" end

  if type(label) ~= "string" then label = tostring(label) end

  -- support shapes: circle, ellipse (pill), default rounded rect
  if label == nil then label = "" end

  -- draw shape
  if state ~= nil then
    cairo.cairo_set_source_rgb(cr, fill_r, fill_g, fill_b)
  end

  if radius == nil then radius = math.min(bw, bh) / 6 end

  if label == nil then label = "" end

  -- isolate path for this element
  cairo.cairo_new_path(cr)

  local used_shape = shape or "rounded"
  if used_shape == "circle" then
    local cx = x + bw/2
    local cy = y + bh/2
    local r = math.min(bw, bh) / 2
    cairo.cairo_arc(cr, cx, cy, r, 0, 2 * math.pi)
    cairo.cairo_fill_preserve(cr)
    cairo.cairo_set_source_rgb(cr, 0, 0, 0)
    cairo.cairo_set_line_width(cr, 2)
    cairo.cairo_stroke(cr)
  elseif used_shape == "ellipse" then
    -- draw ellipse by scaling a unit circle
    cairo.cairo_save(cr)
    local cx = x + bw/2
    local cy = y + bh/2
    cairo.cairo_translate(cr, cx, cy)
    cairo.cairo_scale(cr, bw/2, bh/2)
    cairo.cairo_arc(cr, 0, 0, 1, 0, 2 * math.pi)
    cairo.cairo_restore(cr)
    cairo.cairo_fill_preserve(cr)
    cairo.cairo_set_source_rgb(cr, 0, 0, 0)
    cairo.cairo_set_line_width(cr, 2)
    cairo.cairo_stroke(cr)
  else
    -- rounded rectangle (pill when radius large)
    if radius >= (bh / 2 - 1) then
      rounded_rect(cr, x, y, bw, bh, bh/2)
    else
      rounded_rect(cr, x, y, bw, bh, radius)
    end
    cairo.cairo_fill_preserve(cr)
    cairo.cairo_set_source_rgb(cr, 0, 0, 0)
    cairo.cairo_set_line_width(cr, 2)
    cairo.cairo_stroke(cr)
  end

  -- ensure path cleared
  cairo.cairo_new_path(cr)

  -- draw centered label
  cairo.cairo_set_source_rgb(cr, 1, 1, 1)
  cairo.cairo_select_font_face(cr, "Sans", 0, 0)
  cairo.cairo_set_font_size(cr, 20)
  local ext = ffi.new("cairo_text_extents_t")
  cairo.cairo_text_extents(cr, label, ext)
  local tw = tonumber(ext.width)
  local th = tonumber(ext.height)
  local tx = x + (bw - tw) / 2 - tonumber(ext.x_bearing)
  local ty = y + (bh - th) / 2 - tonumber(ext.y_bearing)
  cairo.cairo_move_to(cr, tx, ty + th)
  cairo.cairo_show_text(cr, label)
end

function M.run(opts)
  opts = opts or {}
  local title = opts.title or "LuaJIT FFI GUI"
  local w = opts.w or 800
  local h = opts.h or 600

  if C.SDL_Init(ffi_sdl.SDL_INIT_VIDEO) ~= 0 then
    error("SDL_Init failed")
  end

  local window = C.SDL_CreateWindow(title, 100, 100, w, h, ffi_sdl.SDL_WINDOW_RESIZABLE)
  if window == nil then error("SDL_CreateWindow failed") end

  local surface = C.SDL_GetWindowSurface(window)
  if surface == nil then error("SDL_GetWindowSurface failed") end

  local current_w = tonumber(surface.w)
  local current_h = tonumber(surface.h)
  local cairo_surf = cairo.cairo_image_surface_create_for_data(ffi.cast("unsigned char *", surface.pixels), ffi_cairo.CAIRO_FORMAT_ARGB32, current_w, current_h, surface.pitch)
  local cr = cairo.cairo_create(cairo_surf)

  -- position any buttons that requested centering
  if #M.buttons > 0 then
    for _, b in ipairs(M.buttons) do
      if b.center then
        local bw = b.w or 160
        local bh = b.h or 64
        b.x, b.y = M.layout_center(current_w, current_h, bw, bh)
        if b.offset_x then b.x = b.x + b.offset_x end
        if b.offset_y then b.y = b.y + b.offset_y end
      end
    end
  end

  local event = ffi.new("SDL_Event")
  local running = true
  local mouse_x = ffi.new("int[1]")
  local mouse_y = ffi.new("int[1]")

  while running do
    local clicked = false
    while C.SDL_PollEvent(event) ~= 0 do
      local et = tonumber(event.type)
      if et == ffi_sdl.SDL_QUIT then
        running = false
      elseif et == ffi_sdl.SDL_MOUSEBUTTONDOWN then
        clicked = true
      end
    end

    -- check for resize each frame by re-querying the window surface
    surface = C.SDL_GetWindowSurface(window)
    if surface == nil then error("SDL_GetWindowSurface failed") end
    local sw, sh = tonumber(surface.w), tonumber(surface.h)
    if sw ~= current_w or sh ~= current_h then
      -- recreate cairo surface/ctx for new size
      cairo.cairo_destroy(cr)
      cairo.cairo_surface_destroy(cairo_surf)
      current_w, current_h = sw, sh
      cairo_surf = cairo.cairo_image_surface_create_for_data(ffi.cast("unsigned char *", surface.pixels), ffi_cairo.CAIRO_FORMAT_ARGB32, current_w, current_h, surface.pitch)
      cr = cairo.cairo_create(cairo_surf)
      -- reposition centered buttons after resize
      if #M.buttons > 0 then
        for _, b in ipairs(M.buttons) do
          if b.center then
            local bw = b.w or 160
            local bh = b.h or 64
            b.x, b.y = M.layout_center(current_w, current_h, bw, bh)
            if b.offset_x then b.x = b.x + b.offset_x end
            if b.offset_y then b.y = b.y + b.offset_y end
          end
        end
      end
    end

    C.SDL_GetMouseState(mouse_x, mouse_y)
    local mx = tonumber(mouse_x[0])
    local my = tonumber(mouse_y[0])

    -- clear
    cairo.cairo_set_source_rgb(cr, 1.0, 1.0, 1.0)
    cairo.cairo_rectangle(cr, 0, 0, surface.w, surface.h)
    cairo.cairo_fill(cr)

    -- static button position
    local rx = 100
    local ry = 100

    -- draw registered buttons
    for _, b in ipairs(M.buttons) do
      local bw, bh = b.w or 160, b.h or 64
      local bx, by = b.x or rx, b.y or ry
      local hover = mx >= bx and mx <= (bx + bw) and my >= by and my <= (by + bh)
      local state = (clicked and hover) and "active" or (hover and "hover" or "normal")
      draw_button(cr, bx, by, bw, bh, b.radius or 12, b.label or "Button", state, b.shape)
      if clicked and hover and type(b.on_click) == "function" then
        -- call callback (safe pcall to avoid crash)
        local ok, err = pcall(b.on_click)
        if not ok then
          io.stderr:write("button callback error: ", tostring(err), "\n")
        end
      end
    end

    cairo.cairo_surface_flush(cairo_surf)
    cairo.cairo_surface_mark_dirty(cairo_surf)
    C.SDL_UpdateWindowSurface(window)

    C.SDL_Delay(16)
  end

  cairo.cairo_destroy(cr)
  cairo.cairo_surface_destroy(cairo_surf)
  C.SDL_Quit()
end

return M
