-- LongChat interface kernel.

local Addon = _G.LongChat
if not Addon then
  return
end

local Interface = Addon.UI or {}
Addon.UI = Interface

local Defaults = {
  width = 660,
  height = 540,
  scale = {
    min = 0.80,
    max = 1.30,
  },
  opacity = {
    min = 0.35,
    max = 1.00,
  },
  fontScale = {
    min = 0.80,
    max = 1.60,
  },
  position = {
    point = "CENTER",
    x = 0,
    y = 0,
  },
}

local Channels = {
  { key = "SAY", label = "Say", allowed = function() return true end },
  { key = "EMOTE", label = "Emote", allowed = function() return true end },
  { key = "PARTY", label = "Party", allowed = function()
    return IsInGroup(LE_PARTY_CATEGORY_HOME) and not IsInRaid(LE_PARTY_CATEGORY_HOME)
  end },
  { key = "INSTANCE_CHAT", label = "Instance", allowed = function()
    return IsInGroup(LE_PARTY_CATEGORY_INSTANCE)
  end },
  { key = "GUILD", label = "Guild", allowed = function() return IsInGuild() end },
  { key = "OFFICER", label = "Officer", allowed = function() return IsInGuild() end },
  { key = "RAID", label = "Raid", allowed = function()
    return IsInRaid(LE_PARTY_CATEGORY_HOME)
  end },
  { key = "RAID_WARNING", label = "Raid Warning", allowed = function()
    return IsInRaid(LE_PARTY_CATEGORY_HOME)
      and (UnitIsGroupLeader("player") or UnitIsGroupAssistant("player"))
  end },
}

local ChannelByKey = {}
Interface.CHANNEL_ORDER = {}
Interface.CHANNEL_LABELS = {}
Interface.THEMES = {
  { key = "dark", label = "LongChat Dark" },
  { key = "wow", label = "WoW Bronze" },
  { key = "parchment", label = "Parchment" },
  { key = "custom", label = "Custom" },
}
Interface.THEME_LABELS = {}
Interface.COMPOSER_SPELL_DISPLAYS = {
  { key = "footer", label = "Footer indicator" },
  { key = "inline", label = "Inline highlights" },
  { key = "off", label = "Off" },
}
Interface.COMPOSER_SPELL_DISPLAY_LABELS = {}

for _, entry in ipairs(Interface.THEMES) do
  Interface.THEME_LABELS[entry.key] = entry.label
end

for _, entry in ipairs(Interface.COMPOSER_SPELL_DISPLAYS) do
  Interface.COMPOSER_SPELL_DISPLAY_LABELS[entry.key] = entry.label
end

for index, entry in ipairs(Channels) do
  ChannelByKey[entry.key] = entry
  Interface.CHANNEL_ORDER[index] = entry.key
  Interface.CHANNEL_LABELS[entry.key] = entry.label
end

Interface.COMPOSER_WIDTH = Defaults.width
Interface.COMPOSER_HEIGHT = Defaults.height
Interface.SCALE_MIN = Defaults.scale.min
Interface.SCALE_MAX = Defaults.scale.max
Interface.OPACITY_MIN = Defaults.opacity.min
Interface.OPACITY_MAX = Defaults.opacity.max
Interface.FONT_SCALE_MIN = Defaults.fontScale.min
Interface.FONT_SCALE_MAX = Defaults.fontScale.max

local function rounded_scale(value)
  value = tonumber(value) or 1
  value = math.max(Defaults.scale.min, math.min(Defaults.scale.max, value))
  return math.floor((value * 100) + 0.5) / 100
end

local function rounded_opacity(value)
  value = tonumber(value) or 1
  value = math.max(Defaults.opacity.min, math.min(Defaults.opacity.max, value))
  return math.floor((value * 100) + 0.5) / 100
end

local function rounded_font_scale(value)
  value = tonumber(value) or 1
  value = math.max(Defaults.fontScale.min, math.min(Defaults.fontScale.max, value))
  return math.floor((value * 100) + 0.5) / 100
end

local function normalize_composer_spell_display(editor)
  local display = editor.composerSpellDisplay
  if Interface.COMPOSER_SPELL_DISPLAY_LABELS[display] then
    return display
  end
  if editor.composerSpellHighlights == false then
    return "off"
  end
  return "inline"
end

local function saved_root()
  LongChatDB = LongChatDB or {}
  LongChatDB.editor = LongChatDB.editor or {}
  if Addon.CurrentEditor then
    return Addon.CurrentEditor()
  end
  return LongChatDB.editor
end

local function ensure_database()
  if Addon.EnsureDB then
    Addon.EnsureDB()
  end

  Addon.DB.editor = Addon.DB.editor or {}
  if Addon.CurrentEditor then
    return Addon.CurrentEditor()
  end
  return Addon.DB.editor
end

local function character_key()
  local name = UnitFullName and UnitFullName("player") or UnitName("player") or "Unknown"
  local realm = GetRealmName and GetRealmName() or "Realm"
  return tostring(name) .. " - " .. tostring(realm)
end

local function normalize_editor(editor)
  editor.characterKey = editor.characterKey or character_key()
  editor.channel = ChannelByKey[editor.channel] and editor.channel or "SAY"
  editor.draft = tostring(editor.draft or "")
  editor.notes = tostring(editor.notes or "")
  if editor.notes == "This is a note." then
    editor.notes = ""
  end
  editor.history = type(editor.history) == "table" and editor.history or {}
  editor.journal = type(editor.journal) == "table" and editor.journal or {}
  editor.activeTab = editor.activeTab == "notes" and "notes" or "compose"
  editor.clearOnSend = editor.clearOnSend == true
  editor.useWowColors = editor.useWowColors == true
  if editor.theme == nil and editor.useWowColors then
    editor.theme = "wow"
  end
  editor.theme = Interface.THEME_LABELS[editor.theme] and editor.theme or "dark"
  editor.customColor = type(editor.customColor) == "table" and editor.customColor or {}
  editor.customColor.r = math.max(0, math.min(1, tonumber(editor.customColor.r) or 0.08))
  editor.customColor.g = math.max(0, math.min(1, tonumber(editor.customColor.g) or 0.10))
  editor.customColor.b = math.max(0, math.min(1, tonumber(editor.customColor.b) or 0.12))
  editor.textColor = type(editor.textColor) == "table" and editor.textColor or {}
  editor.textColor.r = math.max(0, math.min(1, tonumber(editor.textColor.r) or 1))
  editor.textColor.g = math.max(0, math.min(1, tonumber(editor.textColor.g) or 1))
  editor.textColor.b = math.max(0, math.min(1, tonumber(editor.textColor.b) or 1))
  editor.composerSpellDisplay = normalize_composer_spell_display(editor)
  editor.composerSpellHighlights = editor.composerSpellDisplay ~= "off"

  editor.minimap = editor.minimap or {}
  if editor.minimap.hide == nil then
    editor.minimap.hide = false
  else
    editor.minimap.hide = editor.minimap.hide == true
  end

  editor.size = editor.size or {}
  editor.size.width = math.max(620, tonumber(editor.size.width) or Defaults.width)
  editor.size.height = math.max(500, tonumber(editor.size.height) or Defaults.height)

  editor.scale = rounded_scale(editor.scale)
  editor.opacity = rounded_opacity(editor.opacity)
  editor.fontScale = rounded_font_scale(editor.fontScale)

  editor.position = editor.position or {}
  editor.position.point = editor.position.point or Defaults.position.point
  editor.position.x = tonumber(editor.position.x) or Defaults.position.x
  editor.position.y = tonumber(editor.position.y) or Defaults.position.y
  return editor
end

local function get_spell_module()
  local spell = Addon.Spellcheck
  if type(spell) ~= "table" then
    return nil
  end
  if type(spell.AttachEditBox) ~= "function" then
    return nil
  end
  if type(spell.RemoveHighlighting) ~= "function" then
    return nil
  end
  return spell
end

local function chat_is_locked()
  return C_ChatInfo
    and C_ChatInfo.InChatMessagingLockdown
    and C_ChatInfo.InChatMessagingLockdown()
end

function Interface:ClampScale(value)
  return rounded_scale(value)
end

function Interface:Editor()
  return normalize_editor(ensure_database())
end

function Interface:Store(key, value)
  self:Editor()[key] = value
  saved_root()[key] = value
end

function Interface:Spell()
  return get_spell_module()
end

function Interface:SpellStatus()
  local spell = get_spell_module()
  if spell and type(spell.GetStatusText) == "function" then
    return spell:GetStatusText()
  end
  return spell and "LongChat spellcheck active." or "LongChat spellcheck unavailable."
end

function Interface:SetStatus(text)
  local line = self.statusText
  if line then
    line:SetText(text or "")
  end
end

function Interface:AttachSpellcheck(editbox)
  local spell = get_spell_module()
  if not spell or not editbox or editbox._LongChatSpellcheckHooked then
    return
  end
  editbox._LongChatSpellcheckHooked = true
  spell:AttachEditBox(editbox, { noInline = editbox._LongChatComposer == true })
end

function Interface:CanUseChannel(channel)
  local entry = ChannelByKey[channel]
  return entry and entry.allowed() or false
end

function Interface:IsRestricted()
  if type(Addon.IsChatModificationRestricted) == "function" and Addon.IsChatModificationRestricted() then
    return true
  end

  local spell = get_spell_module()
  if spell and type(spell.IsRestricted) == "function" and spell:IsRestricted() then
    return true
  end

  return (InCombatLockdown and InCombatLockdown())
    or (UnitAffectingCombat and UnitAffectingCombat("player"))
    or chat_is_locked()
    or false
end

function Interface:RememberPosition(frame)
  if not frame or not frame.GetPoint then
    return
  end

  local point, _, _, x, y = frame:GetPoint()
  local position = self:Editor().position
  position.point = point or Defaults.position.point
  position.x = x or Defaults.position.x
  position.y = y or Defaults.position.y
end

function Interface:RememberDraft()
  local editbox = self.editBox
  if editbox then
    self:Editor().draft = editbox:GetText() or ""
  end
end

function Interface:RememberNotes()
  local notes = self.notesBox
  if notes then
    local editor = self:Editor()
    editor.notes = notes:GetText() or ""
  end
end

function Interface:RecordHistory(text, channel)
  text = tostring(text or "")
  if text == "" then
    return
  end

  local editor = self:Editor()
  table.insert(editor.history, 1, {
    text = text,
    channel = channel or editor.channel or "SAY",
    time = date and date("%m/%d %H:%M") or "",
  })

  while #editor.history > 20 do
    table.remove(editor.history)
  end
end

function Interface:HistoryText()
  local history = self:Editor().history
  if #history == 0 then
    return "No sent LongChat messages yet."
  end

  local lines = {}
  for index, item in ipairs(history) do
    local label = self:LabelFor(item.channel)
    local stamp = item.time and item.time ~= "" and item.time or "recent"
    lines[#lines + 1] = ("%d. %s - %s"):format(index, stamp, label)
    lines[#lines + 1] = tostring(item.text or "")
    lines[#lines + 1] = ""
  end
  return table.concat(lines, "\n")
end

function Interface:SaveJournalEntry(text, title)
  text = tostring(text or "")
  text = text:gsub("^%s+", ""):gsub("%s+$", "")
  if text == "" then
    return false
  end

  title = tostring(title or ""):gsub("^%s+", ""):gsub("%s+$", "")
  if title == "" then
    title = "Untitled event"
  end
  if #title > 54 then
    title = title:sub(1, 51) .. "..."
  end

  local editor = self:Editor()
  local journal = editor.journal
  table.insert(journal, 1, {
    title = title,
    text = text,
    time = date and (date("%m/%d/%y %I:%M %p") .. " LT") or "",
  })

  while #journal > 50 do
    table.remove(journal)
  end
  editor.journal = journal

  return true
end

function Interface:JournalText()
  local journal = self:Editor().journal
  if #journal == 0 then
    return "No notable RP events saved yet."
  end

  local lines = {}
  for index, item in ipairs(journal) do
    local stamp = item.time and item.time ~= "" and item.time or "undated"
    lines[#lines + 1] = ("%d. %s - %s"):format(index, stamp, tostring(item.title or "Event"))
  end
  return table.concat(lines, "\n")
end

function Interface:LabelFor(channel)
  local entry = ChannelByKey[channel]
  return entry and entry.label or tostring(channel or "SAY")
end

function Interface:Button(parent, text, width, onClick)
  local button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
  button:SetSize(width or 90, 24)
  button:SetText(text or "")
  button:SetScript("OnClick", onClick)
  return button
end

function Interface:ApplyScale()
  if not self.frame then
    return
  end

  local editor = self:Editor()
  self.frame:SetSize(editor.size.width or Defaults.width, editor.size.height or Defaults.height)
  self.frame:SetScale(editor.scale or 1)
end

function Interface:RefreshFromProfile()
  local editor = self:Editor()
  if self.editBox then
    self.editBox:SetText(editor.draft or "")
  end
  if self.notesBox then
    self.notesBox:SetText(editor.notes or "")
  end
  self.selectedJournalIndex = nil
  if self.composerClearCheck then
    self.composerClearCheck:SetChecked(editor.clearOnSend)
  end
  self:SetChannel(editor.channel or "SAY")
  self:ApplyScale()
  if self.RefreshTheme then
    self:RefreshTheme()
  end
  if self.RefreshTextColor then
    self:RefreshTextColor()
  end
  if self.RefreshComposerFontScale then
    self:RefreshComposerFontScale()
  end
  if self.RefreshComposerSpellcheckMode then
    self:RefreshComposerSpellcheckMode()
  end
  if self.RefreshHistory then
    self:RefreshHistory()
  end
  if self.frame then
    self:SetTab(editor.activeTab or "compose")
  end
  self:SetStatus("Profile loaded: " .. tostring(editor.profileName or "Profile"))
end

function Addon.FlushSettings()
  local editor = Interface:Editor()
  local dropdown = Interface.channelDropdown
  if dropdown and dropdown.GetValue then
    editor.channel = dropdown:GetValue() or editor.channel
  end

  Interface:Store("clearOnSend", editor.clearOnSend == true)
  Interface:Store("useWowColors", editor.useWowColors == true)
  Interface:Store("theme", editor.theme or "dark")
  Interface:Store("customColor", editor.customColor or {})
  Interface:Store("textColor", editor.textColor or {})
  Interface:Store("composerSpellDisplay", editor.composerSpellDisplay or "footer")
  Interface:Store("composerSpellHighlights", editor.composerSpellDisplay ~= "off")
  Interface:Store("notes", editor.notes or "")
  Interface:Store("journal", editor.journal or {})
  Interface:Store("characterKey", editor.characterKey or character_key())
  Interface:Store("history", editor.history or {})
  Interface:Store("activeTab", editor.activeTab or "compose")
  Interface:Store("scale", rounded_scale(editor.scale))
  Interface:Store("opacity", rounded_opacity(editor.opacity))
  Interface:Store("fontScale", rounded_font_scale(editor.fontScale))

  local saved = saved_root()
  saved.minimap = saved.minimap or {}
  saved.minimap.hide = editor.minimap and editor.minimap.hide == true
end

function Addon.SetComposerScale(scale)
  Interface:Store("scale", rounded_scale(scale))
  Interface:ApplyScale()
end

function Addon.SetComposerOpacity(opacity)
  Interface:Store("opacity", rounded_opacity(opacity))
  if Interface.RefreshTheme then
    Interface:RefreshTheme()
  end
end

function Addon.SetComposerFontScale(scale)
  Interface:Store("fontScale", rounded_font_scale(scale))
  if Interface.RefreshComposerFontScale then
    Interface:RefreshComposerFontScale()
  end
end

function Addon.SetComposerTheme(theme)
  Interface:Store("theme", Interface.THEME_LABELS[theme] and theme or "dark")
  Interface:Store("useWowColors", theme == "wow")
  if Interface.RefreshTheme then
    Interface:RefreshTheme()
  end
end

function Addon.SetComposerCustomColor(r, g, b)
  local editor = Interface:Editor()
  editor.customColor = editor.customColor or {}
  editor.customColor.r = math.max(0, math.min(1, tonumber(r) or editor.customColor.r or 0.08))
  editor.customColor.g = math.max(0, math.min(1, tonumber(g) or editor.customColor.g or 0.10))
  editor.customColor.b = math.max(0, math.min(1, tonumber(b) or editor.customColor.b or 0.12))
  Interface:Store("customColor", editor.customColor)
  Addon.SetComposerTheme("custom")
end

function Addon.SetComposerTextColor(r, g, b)
  local editor = Interface:Editor()
  editor.textColor = editor.textColor or {}
  editor.textColor.r = math.max(0, math.min(1, tonumber(r) or editor.textColor.r or 1))
  editor.textColor.g = math.max(0, math.min(1, tonumber(g) or editor.textColor.g or 1))
  editor.textColor.b = math.max(0, math.min(1, tonumber(b) or editor.textColor.b or 1))
  Interface:Store("textColor", editor.textColor)
  if Interface.RefreshTextColor then
    Interface:RefreshTextColor()
  end
end

function Addon.ResetComposerTextColor()
  Addon.SetComposerTextColor(1, 1, 1)
end

function Addon.SetComposerSpellHighlights(enabled)
  Addon.SetComposerSpellDisplay(enabled == true and "footer" or "off")
end

function Addon.SetComposerSpellDisplay(display)
  display = Interface.COMPOSER_SPELL_DISPLAY_LABELS[display] and display or "footer"
  Interface:Store("composerSpellDisplay", display)
  Interface:Store("composerSpellHighlights", display ~= "off")
  if Interface.RefreshComposerSpellcheckMode then
    Interface:RefreshComposerSpellcheckMode()
  end
end
