-- Sleek RP - Warlock Pets (Bubble.lua)
-- Deux styles de bulles : nameplate (Bubble_01) et portrait PetFrame (Bubble_02).
-- Artwork "scale" ne modifie PAS la taille du texte. Le texte s'ajuste (<=2 lignes).
-- Ajout : mini-titre d'action (opts.action) en petit et gras, au-dessus du texte principal.

local ADDON_NAME, SRPWL = ...
if not SRPWL then return end

-- ========= Chemins & police =========
local TEX_NP  = "Interface\\AddOns\\SleekRP_WarlockPets\\Art\\Bubble_01.tga" -- nameplate
local TEX_PF  = "Interface\\AddOns\\SleekRP_WarlockPets\\Art\\Bubble_02.tga" -- portrait PetFrame
local FONT    = "Interface\\AddOns\\SleekRP_WarlockPets\\Fonts\\PatrickHand-Regular.ttf"

-- ========= DB & défauts =========
SleekRP_WLP_DB = SleekRP_WLP_DB or {}
local DB = SleekRP_WLP_DB

-- Valeurs par défaut (inclut textSize / textPad pour que les réglages influent la bulle)
local NP_DEFAULT = { offX=27,  offY=-22, scale=0.88, width=357, height=174, textSize=16, textPad=0 }

-- >>> Portrait defaults (issus de la PJ)
local PF_DEFAULT = {
  offX   = -7,
  offY   = 91,
  scale  = 0.96,
  width  = 377,
  height = 192,
  side   = "right", -- "left"|"right"
  textSize = 15,
  textPad  = 0,
}

-- Permet d'imposer les defaults en mode "portrait" (ignorer DB). Actif par défaut comme demandé.
SRPWL.PORTRAIT_FORCE_DEFAULTS = (SRPWL.PORTRAIT_FORCE_DEFAULTS ~= false)

DB.bubbleNP = DB.bubbleNP or {}
for k,v in pairs(NP_DEFAULT) do if DB.bubbleNP[k] == nil then DB.bubbleNP[k] = v end end

DB.bubblePF = DB.bubblePF or {}
for k,v in pairs(PF_DEFAULT) do if DB.bubblePF[k] == nil then DB.bubblePF[k] = v end end

-- ========= Constantes de zone texte =========
-- Artwork actuel : 512x256, fenêtre texte 144px de haut en HAUT de l’image.
local TEX_W, TEX_H     = 512, 256
local TEXT_ZONE_H      = 144
local TEXT_ZONE_T      = 0

-- Insets de base (le padding utilisateur s’ajoute dessus)
local BASE_INSET_L, BASE_INSET_R = 44, 44
local ICON_W, ICON_H   = 18, 18
local ICON_GAP         = 8

-- Limites de taille police
local ABS_MIN_FONT = 10
local ABS_MAX_FONT = 32

-- Durée d'affichage (par défaut +2s vis-à-vis des 2.5s initiales)
local SHOW_SEC = (SRPWL.BUBBLE_DURATION and tonumber(SRPWL.BUBBLE_DURATION)) or 4.5

-- ========= Helpers d’ancrage =========
local function GetPetPlate()
  if C_NamePlate and C_NamePlate.GetNamePlateForUnit then
    return C_NamePlate.GetNamePlateForUnit("pet")
  end
end
local function GetPlayerPlate()
  if C_NamePlate and C_NamePlate.GetNamePlateForUnit then
    return C_NamePlate.GetNamePlateForUnit("player")
  end
end

-- Si Core a défini _BestAnchor, on l’utilise, sinon fallback.
function SRPWL.BestAnchor()
  if type(SRPWL._BestAnchor) == "function" then
    return SRPWL._BestAnchor()
  end

  local petPlate = GetPetPlate()
  if petPlate then return petPlate, "BOTTOM", "TOP", 0, 28 end

  if _G.PetFrame and _G.PetFrame:IsShown() then
    return _G.PetFrame, "BOTTOM", "TOP", 0, 10
  end

  local playerPlate = GetPlayerPlate()
  if playerPlate then return playerPlate, "BOTTOM", "TOP", 0, 72 end

  return UIParent, "CENTER", "CENTER", 0, 140
end

-- Détecte le « type » d’ancrage : "portrait" si PetFrame, sinon "nameplate".
local function AnchorType(parent)
  if parent and _G.PetFrame and parent == _G.PetFrame then
    return "portrait"
  end
  return "nameplate"
end

-- ========= Frame bulle =========
local bubble, bgTX, iconTX, textFS, measureFS, actionFS
local lastText, lastPetKey, lastIcon, lastOpts

local function EnsureBubble()
  if bubble then return end

  bubble = CreateFrame("Frame", "SRPWL_Bubble", UIParent)
  bubble:SetClampedToScreen(true)
  bubble:SetFrameStrata("LOW")     -- sous la barre/portrait joueur
  bubble:SetFrameLevel(1)
  bubble:EnableMouse(false)
  bubble:SetClipsChildren(true)    -- évite que l’artwork scale déborde

  -- Taille logique (sera remplacée à chaque affichage)
  bubble:SetSize(DB.bubbleNP.width or NP_DEFAULT.width, DB.bubbleNP.height or NP_DEFAULT.height)

  -- Artwork (taille = taille du cadre ; l’échelle de l’artwork se fait via bgTX:SetScale)
  bgTX = bubble:CreateTexture(nil, "BACKGROUND")
  bgTX:SetPoint("CENTER")
  bgTX:SetSize(bubble:GetSize())
  bgTX:SetTexCoord(0,1,0,1)
  bgTX:SetTexture(TEX_NP)

  -- Icône
  iconTX = bubble:CreateTexture(nil, "ARTWORK")
  iconTX:SetSize(ICON_W, ICON_H)

  -- Titre d'action (petit et gras)
  actionFS = bubble:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
  actionFS:SetTextColor(0,0,0,1)
  actionFS:SetJustifyH("LEFT")
  actionFS:SetJustifyV("TOP")
  actionFS:SetWordWrap(false)

  -- Texte principal
  textFS = bubble:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  textFS:SetTextColor(0,0,0,1)
  textFS:SetShadowColor(0,0,0,0.18)
  textFS:SetShadowOffset(1,-1)
  textFS:SetJustifyH("LEFT")
  textFS:SetJustifyV("TOP")
  textFS:SetWordWrap(true)

  -- Mesure
  measureFS = bubble:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
  measureFS:SetWordWrap(false)
  measureFS:Hide()
end

-- Mesure : (height, lines, lineH)
local function MeasureText(text, textW)
  textFS:ClearAllPoints()
  textFS:SetWidth(textW)
  textFS:SetText(text or "")

  local th    = textFS:GetStringHeight() or 0
  local lines = (textFS.GetNumLines and textFS:GetNumLines()) or 0
  local _, fs = textFS:GetFont()
  local lineH = (fs or 16) + 2

  if th <= 0 and lines > 0 then th = lines * lineH end
  if th <= 0 then th = lineH end
  if lines > 0 then th = math.max(th, lines * lineH) end
  return th, lines, lineH
end

-- Essaie de forcer <= 2 lignes à partir d’un "baseSize"; minSize borné par ABS_MIN_FONT.
local function FitFontToTwoLines(text, maxTextW, baseSize)
  local base = math.min(ABS_MAX_FONT, math.max(ABS_MIN_FONT, baseSize or 16))
  local minS = math.max(ABS_MIN_FONT, base - 6)

  local size = base
  while size >= minS do
    textFS:SetFont(FONT, size, "")
    measureFS:SetFont(FONT, size, "")
    local _, lines = MeasureText(text, maxTextW)
    if lines <= 2 then
      return size, lines
    end
    size = size - 1
  end
  textFS:SetFont(FONT, minS, "")
  measureFS:SetFont(FONT, minS, "")
  local _, lines = MeasureText(text, maxTextW)
  return minS, lines
end

-- Positionnement du contenu dans la fenêtre texte (144px en haut de 512x256).
local function LayoutContent(text, iconID, frameW, frameH, cfg, opts)
  opts = opts or {}

  -- Insets effectifs (padding utilisateur ajouté aux insets de base)
  local pad = tonumber(cfg.textPad) or 0
  if pad < 0 then pad = 0 end
  local INSET_L = BASE_INSET_L + pad
  local INSET_R = BASE_INSET_R + pad

  -- Icône
  iconTX:SetTexture(iconID or 132161)

  -- Largeur dispo pour le texte
  local maxTextW = frameW - (INSET_L + ICON_W + ICON_GAP + INSET_R)
  if maxTextW < 60 then maxTextW = 60 end

  -- Décalage vertical configurable (ex: ~18 px ≈ 0.5 cm)
  local extraY = tonumber(SleekRP_WLP_DB.action_offset_y) or 18

  -- Titre d'action
  local actionLabel = (type(opts.action)=="string" and opts.action ~= "") and opts.action or nil
  local titleH = 0
  local zoneTop_px = (TEXT_ZONE_T / TEX_H) * frameH
  if actionLabel then
    local titleSize = math.max(ABS_MIN_FONT, (cfg.textSize or 16) - 2)
    actionFS:SetFont(FONT, titleSize, "")
    actionFS:SetText(actionLabel)
    actionFS:ClearAllPoints()
    actionFS:SetPoint("TOPLEFT", bubble, "TOPLEFT", INSET_L, -(zoneTop_px + 4 + extraY))
    actionFS:SetWidth(maxTextW + ICON_W + ICON_GAP)
    titleH = actionFS:GetStringHeight() or 0
  else
    actionFS:SetText("")
  end

  -- Ajuste la police pour <= 2 lignes (base = cfg.textSize)
  FitFontToTwoLines(text or "", maxTextW, cfg.textSize or 16)

  -- Mesure finale du texte
  local textH    = select(1, MeasureText(text, maxTextW))
  local contentH = math.max(ICON_H, textH)

  -- Mappe la zone texte (144px/TEX_H) sur la hauteur réelle du cadre
  local zoneH_px   = (TEXT_ZONE_H / TEX_H) * frameH
  local topOffset  = zoneTop_px + math.max(0, math.floor((zoneH_px - (contentH + titleH))/2 + 0.5)) + extraY

  -- Place icône & texte (le texte sous le titre quand il existe)
  iconTX:ClearAllPoints()
  iconTX:SetPoint("TOPLEFT", bubble, "TOPLEFT", INSET_L, -(topOffset + titleH + 4))

  textFS:ClearAllPoints()
  textFS:SetPoint("TOPLEFT", iconTX, "TOPRIGHT", ICON_GAP, 0)
  textFS:SetPoint("TOPRIGHT", bubble, "TOPRIGHT", -INSET_R, -(topOffset + titleH + 4))
end

local function HideBubble()
  if bubble and bubble:IsShown() then bubble:Hide() end
end

-- ========= Utils =========
local function shallowCopy(tbl)
  if not tbl then return nil end
  local out = {}
  for k,v in pairs(tbl) do out[k] = v end
  return out
end

-- ========= API =========
-- Montre la bulle adaptée à l’ancrage courant (nameplate / portrait).
-- opts.action (string) : petit titre affiché au-dessus du texte (ex: "Mount summoned")
-- opts.art (number|texturePath) : icône à afficher
function SRPWL.ShowBubble(text, petKey, opts)
  if not text or text == "" then return end
  EnsureBubble()

  -- Mémorise pour ApplyUISettings()
  local iconID = (opts and opts.art)
  if not iconID and SRPWL.DemonIcons and petKey then
    iconID = SRPWL.DemonIcons[petKey]
  end
  lastText, lastPetKey, lastIcon, lastOpts = text, petKey, iconID, opts

  -- Choix de l’ancrage
  local parent, p1, p2, ox, oy = SRPWL.BestAnchor()
  local kind = (parent and _G.PetFrame and parent == _G.PetFrame) and "portrait" or "nameplate"

  -- Config & texture selon le type
  local cfgSrc, tex
  if kind == "portrait" then
    if SRPWL.PORTRAIT_FORCE_DEFAULTS then
      cfgSrc = PF_DEFAULT
    else
      cfgSrc = shallowCopy(PF_DEFAULT)
      for k,v in pairs(DB.bubblePF) do if v ~= nil then cfgSrc[k] = v end end
    end
    tex = TEX_PF
  else
    cfgSrc = DB.bubbleNP
    tex = TEX_NP
  end

  local cfg = cfgSrc

  -- Taille du cadre (ne scale PAS le texte)
  bubble:SetSize(cfg.width, cfg.height)

  -- Artwork : taille = taille du cadre, mais on applique un scale SUR LA TEXTURE uniquement
  bgTX:ClearAllPoints()
  bgTX:SetPoint("CENTER", bubble, "CENTER", 0, 0)
  bgTX:SetSize(cfg.width, cfg.height)
  bgTX:SetTexture(tex)
  bgTX:SetScale(cfg.scale or 1.0)

  -- Miroir horizontal si "portrait" et side = "left"
  if kind == "portrait" and (cfg.side == "left") then
    bgTX:SetTexCoord(1,0, 0,1)
  else
    bgTX:SetTexCoord(0,1, 0,1)
  end

  -- Placement du cadre
  bubble:ClearAllPoints()
  if kind == "portrait" and _G.PetFrame and _G.PetFrame:IsShown() then
    if cfg.side == "left" then
      bubble:SetPoint("RIGHT", _G.PetFrame, "LEFT", -(cfg.offX or 0), cfg.offY or 0)
    else
      bubble:SetPoint("LEFT",  _G.PetFrame, "RIGHT",  cfg.offX or 0,  cfg.offY or 0)
    end
  else
    bubble:SetPoint(p1, parent, p2, (ox or 0) + (DB.bubbleNP.offX or 0), (oy or 0) + (DB.bubbleNP.offY or 0))
  end

  -- Mise en page du contenu (dans la fenêtre texte)
  LayoutContent(text, iconID, cfg.width, cfg.height, cfg, opts)

  -- Affiche
  bubble:Show()

  -- Reflow 1 frame plus tard (fiabilise la mesure lignes/hauteur)
  C_Timer.After(0, function()
    if bubble and bubble:IsShown() then
      LayoutContent(text, iconID, cfg.width, cfg.height, cfg, opts)
    end
  end)

  C_Timer.After((SRPWL.BUBBLE_DURATION and tonumber(SRPWL.BUBBLE_DURATION)) or SHOW_SEC, HideBubble)
end

-- Utilitaire pour l’UI Options : reset défauts
function SRPWL.Bubble_ResetDefaults(which)
  if which == "NP" or which == nil then
    for k,v in pairs(NP_DEFAULT) do DB.bubbleNP[k] = v end
  end
  if which == "PF" or which == nil then
    for k,v in pairs(PF_DEFAULT) do DB.bubblePF[k] = v end
  end
end

-- Appliquer à chaud les réglages depuis Options.lua
function SRPWL.ApplyUISettings()
  if bubble and bubble:IsShown() and lastText then
    SRPWL.ShowBubble(lastText, lastPetKey, { art = lastIcon, action = lastOpts and lastOpts.action })
  end
end

-- ========= Debug slash =========
SLASH_SRPWLBUBBLE1 = "/srpwb"
SlashCmdList["SRPWLBUBBLE"] = function(msg)
  DB.debug = not DB.debug
  print("|cffa9a9ff[SRP-BUBBLE]|r Debug:", DB.debug and "ON" or "OFF")
end
