local core = require "core"
local config = require "core.config"
local style = require "core.style"
local common = require "core.common"
local Object = require "core.object"

---@class core.view.position
---@field x number
---@field y number

---@class core.view.scroll
---@field x number
---@field y number
---@field to core.view.position

---@class core.view.thumbtrack
---@field thumb number
---@field track number

---@class core.view.thumbtrackwidth
---@field thumb number
---@field track number
---@field to core.view.thumbtrack

---@class core.view.scrollbar
---@field x core.view.thumbtrack
---@field y core.view.thumbtrack
---@field w core.view.thumbtrackwidth
---@field h core.view.thumbtrack

---@class core.view.increment
---@field value number
---@field to number

---@alias core.view.cursor "'arrow'" | "'ibeam'" | "'sizeh'" | "'sizev'" | "'hand'"

---@alias core.view.mousebutton "'left'" | "'right'"

---@alias core.view.context "'application'" | "'session'"

---Base view.
---@class core.view : core.object
---@field context core.view.context
---@field super core.object
---@field position core.view.position
---@field size core.view.position
---@field scroll core.view.scroll
---@field cursor core.view.cursor
---@field scrollable boolean
---@field scrollbar core.view.scrollbar
---@field scrollbar_alpha core.view.increment
local View = Object:extend()

-- context can be "application" or "session". The instance of objects
-- with context "session" will be closed when a project session is
-- terminated. The context "application" is for functional UI elements.
View.context = "application"

function View:new()
  self.position = { x = 0, y = 0 }
  self.size = { x = 0, y = 0 }
  self.scroll = { x = 0, y = 0, to = { x = 0, y = 0 } }
  self.cursor = "arrow"
  self.scrollable = false
  self.scrollbar = {
    x = { thumb = 0, track = 0 },
    y = { thumb = 0, track = 0 },
    w = { thumb = 0, track = 0, to = { thumb = 0, track = 0 } },
    h = { thumb = 0, track = 0 },
  }
  self.scrollbar_alpha = { value = 0, to = 0 }
end

function View:move_towards(t, k, dest, rate, name)
  if type(t) ~= "table" then
    return self:move_towards(self, t, k, dest, rate, name)
  end
  local val = t[k]
  local diff = math.abs(val - dest)
  if not config.transitions or diff < 0.5 or config.disabled_transitions[name] then
    t[k] = dest
  else
    rate = rate or 0.5
    if config.fps ~= 60 or config.animation_rate ~= 1 then
      local dt = 60 / config.fps
      rate = 1 - common.clamp(1 - rate, 1e-8, 1 - 1e-8)^(config.animation_rate * dt)
    end
    t[k] = common.lerp(val, dest, rate)
  end
  if diff > 1e-8 then
    core.redraw = true
  end
end


function View:try_close(do_close)
  do_close()
end


---@return string
function View:get_name()
  return "---"
end


---@return number
function View:get_scrollable_size()
  return math.huge
end


---@return number x
---@return number y
---@return number width
---@return number height
function View:get_scrollbar_track_rect()
  local sz = self:get_scrollable_size()
  if sz <= self.size.y or sz == math.huge then
    return 0, 0, 0, 0
  end
  local width = style.scrollbar_size
  if self.hovered_scrollbar_track or self.dragging_scrollbar then
    width = style.expanded_scrollbar_size
  end
  return
    self.position.x + self.size.x - width,
    self.position.y,
    width,
    self.size.y
end


---@return number x
---@return number y
---@return number width
---@return number height
function View:get_scrollbar_rect()
  local sz = self:get_scrollable_size()
  if sz <= self.size.y or sz == math.huge then
    return 0, 0, 0, 0
  end
  local h = math.max(20, self.size.y * self.size.y / sz)
  local width = style.scrollbar_size
  if self.hovered_scrollbar_track or self.dragging_scrollbar then
    width = style.expanded_scrollbar_size
  end
  return
    self.position.x + self.size.x - width,
    self.position.y + self.scroll.y * (self.size.y - h) / (sz - self.size.y),
    width,
    h
end


---@param x number
---@param y number
---@return boolean
function View:scrollbar_overlaps_point(x, y)
  local sx, sy, sw, sh = self:get_scrollbar_rect()
  return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh
end

---@param x number
---@param y number
---@return boolean
function View:scrollbar_track_overlaps_point(x, y)
  local sx, sy, sw, sh = self:get_scrollbar_track_rect()
  return x >= sx - style.scrollbar_size * 3 and x < sx + sw and y > sy and y <= sy + sh
end


---@param button core.view.mousebutton
---@param x number
---@param y number
---@param clicks integer
---return boolean
function View:on_mouse_pressed(button, x, y, clicks)
  if self:scrollbar_track_overlaps_point(x, y) then
    if self:scrollbar_overlaps_point(x, y) then
      self.dragging_scrollbar = true
    else
      local _, _, _, sh = self:get_scrollbar_rect()
      local ly = (y - self.position.y) - sh / 2
      local pct = common.clamp(ly / self.size.y, 0, 100)
      self.scroll.to.y = self:get_scrollable_size() * pct
    end
    return true
  end
end


---@param button core.view.mousebutton
---@param x number
---@param y number
function View:on_mouse_released(button, x, y)
  self.dragging_scrollbar = false
end


---@param x number
---@param y number
---@param dx number
---@param dy number
function View:on_mouse_moved(x, y, dx, dy)
  if self.dragging_scrollbar then
    local delta = self:get_scrollable_size() / self.size.y * dy
    self.scroll.to.y = self.scroll.to.y + delta
    if not config.animate_drag_scroll then
      self:clamp_scroll_position()
      self.scroll.y = self.scroll.to.y
    end
  end
  self.hovered_scrollbar = self:scrollbar_overlaps_point(x, y)
  self.hovered_scrollbar_track = self.hovered_scrollbar or self:scrollbar_track_overlaps_point(x, y)
end


function View:on_mouse_left()
  self.hovered_scrollbar = false
  self.hovered_scrollbar_track = false
end


---@param filename string
---@param x number
---@param y number
---@return boolean
function View:on_file_dropped(filename, x, y)
  return false
end


---@param text string
function View:on_text_input(text)
  -- no-op
end

---@param y number
---@return boolean
function View:on_mouse_wheel(y)

end

function View:get_content_bounds()
  local x = self.scroll.x
  local y = self.scroll.y
  return x, y, x + self.size.x, y + self.size.y
end


---@return number x
---@return number y
function View:get_content_offset()
  local x = common.round(self.position.x - self.scroll.x)
  local y = common.round(self.position.y - self.scroll.y)
  return x, y
end


function View:clamp_scroll_position()
  local max = self:get_scrollable_size() - self.size.y
  self.scroll.to.y = common.clamp(self.scroll.to.y, 0, max)
end


function View:update_scrollbar()
    local x, y, w, h = self:get_scrollbar_rect()
    self.scrollbar.w.to.thumb = w
    self:move_towards(self.scrollbar.w, "thumb", self.scrollbar.w.to.thumb, 0.3, "scroll")
    self.scrollbar.x.thumb = x + w - self.scrollbar.w.thumb
    self.scrollbar.y.thumb = y
    self.scrollbar.h.thumb = h

    local x, y, w, h = self:get_scrollbar_track_rect()
    self.scrollbar.w.to.track = w
    self:move_towards(self.scrollbar.w, "track", self.scrollbar.w.to.track, 0.3, "scroll")
    self.scrollbar.x.track = x + w - self.scrollbar.w.track
    self.scrollbar.y.track = y
    self.scrollbar.h.track = h

    -- we use 100 for a smoother transition
    self.scrollbar_alpha.to = (self.hovered_scrollbar_track or self.dragging_scrollbar) and 100 or 0
    self:move_towards(self.scrollbar_alpha, "value", self.scrollbar_alpha.to, 0.3, "scroll")
end


function View:update()
  self:clamp_scroll_position()
  self:move_towards(self.scroll, "x", self.scroll.to.x, 0.3, "scroll")
  self:move_towards(self.scroll, "y", self.scroll.to.y, 0.3, "scroll")

  self:update_scrollbar()
end


---@param color renderer.color
function View:draw_background(color)
  local x, y = self.position.x, self.position.y
  local w, h = self.size.x, self.size.y
  renderer.draw_rect(x, y, w, h, color)
end


function View:draw_scrollbar_track()
  if not (self.hovered_scrollbar_track or self.dragging_scrollbar)
     and self.scrollbar_alpha.value == 0 then
    return
  end
  local color = { table.unpack(style.scrollbar_track) }
  color[4] = color[4] * self.scrollbar_alpha.value / 100
  renderer.draw_rect(self.scrollbar.x.track, self.scrollbar.y.track,
                     self.scrollbar.w.track, self.scrollbar.h.track, color)
end


function View:draw_scrollbar_thumb()
  local highlight = self.hovered_scrollbar or self.dragging_scrollbar
  local color = highlight and style.scrollbar2 or style.scrollbar
  renderer.draw_rect(self.scrollbar.x.thumb, self.scrollbar.y.thumb,
                     self.scrollbar.w.thumb, self.scrollbar.h.thumb, color)
end


function View:draw_scrollbar()
  self:draw_scrollbar_track()
  self:draw_scrollbar_thumb()
end


function View:draw()
end


return View