--========================================================
-- Inspect Items Framework - 3D Item Preview (Build 42)
-- Escala de modelo (no zoom de camara)
--========================================================

require "ISUI/ISUI3DScene"
local RotationProfile = require("InspectSystem/Profiles/Inspect_RotationProfile")

local unpack = unpack or table.unpack

local AUTO_FIT_TARGET = 2.2
local DEFAULT_ZOOM = 4.0
local DEFAULT_WEAPON_ZOOM = 1.8

Inspect3DPreview = {}
Inspect3DPreview.__index = Inspect3DPreview

----------------------------------------------------------
-- Helper to safely call UI3DScene API (supports fromLuaN)
----------------------------------------------------------
local function sceneCall(scene, method, ...)
    if not scene or not method then return false end

    local args = { ... }
    local argc = #args

    -- Direct method
    local direct = scene[method]
    if direct then
        local ok, res = pcall(direct, scene, unpack(args))
        if ok then return res ~= nil and res or true end
    end

    -- Bridge via fromLuaN on scene
    local bridgeName = "fromLua" .. tostring(argc)
    local bridge = scene[bridgeName]
    if bridge then
        local ok, res = pcall(bridge, scene, method, unpack(args))
        if ok then return res ~= nil and res or true end
    end

    -- Bridge via fromLuaN on javaObject
    local jo = scene.javaObject
    if jo then
        local jBridge = jo[bridgeName]
        if jBridge then
            local ok, res = pcall(jBridge, jo, method, unpack(args))
            if ok then return res ~= nil and res or true end
        end
    end

    return false
end

----------------------------------------------------------
-- Resolve model name for any item
----------------------------------------------------------
local function getModelName(item)
    if not item then return nil end

    local m = item.getWorldStaticModel and item:getWorldStaticModel()
    if m then return m end

    m = item.getStaticModel and item:getStaticModel()
    if m then return m end

    m = item.getWeaponSprite and item:getWeaponSprite()
    if m then return m end

    m = item.getModel and item:getModel()
    if m then return m end

    return nil
end

----------------------------------------------------------
-- Constructor
----------------------------------------------------------
function Inspect3DPreview:new(item)
    local o = {}
    setmetatable(o, self)

    o.item = item
    o.scene = nil
    o.loadedModel = nil
    o.valid3D = false
    o.didAutoFit = false
    o.didCenter = false
    o.baseOffsetX = 0
    o.baseOffsetY = 0
    o.baseOffsetZ = 0

    o.angleX = 0.0
    o.angleY = 90.0
    o.angleZ = 0.0

    o.baseScale = 1.8 -- base scale per item type
    o.scale     = DEFAULT_ZOOM -- user zoom factor (wheel)

    return o
end

----------------------------------------------------------
-- Create 3D scene
----------------------------------------------------------
function Inspect3DPreview:createScene(x, y, w, h, parent)
    if self.scene then return self.scene end

    local scene = ISUI3DScene:new(x, y, w, h)
    scene:initialise()
    scene:instantiate()

    scene:setView("UserDefined")
    sceneCall(scene, "setGridPlane", "XZ")
    sceneCall(scene, "setMaxZoom", 25)
    sceneCall(scene, "setDrawGrid", false)

    -- Redirect wheel to preview (avoid internal zoom)
    local preview = self
    function scene:onMouseWheel(del)
        if preview and preview.onMouseWheel then
            preview:onMouseWheel(del)
        end
        return true
    end

    parent:addChild(scene)
    self.scene = scene

    -- Doble clic para reset + autofit
    function scene:onMouseDoubleClick(x, y)
        if preview and preview.onMouseDoubleClick then
            preview:onMouseDoubleClick(x, y)
        end
        return true
    end

    --------------------------------------------------------------
    -- Liberar mouse en PHOTO MODE para que no bloquee el resize
    --------------------------------------------------------------
    function scene:onMouseDown(x, y)
        if preview.parent and preview.parent.photoMode then
            -- Permitir que otros widgets (resize) reciban el mouse
            return false
        end
        -- Comportamiento normal fuera de Photo Mode
        return false -- deja pasar eventos normales al parent
    end

    function scene:onMouseMove(dx, dy)
        if preview.parent and preview.parent.photoMode then
            return false
        end
        return false
    end

    function scene:onMouseUp(x, y)
        if preview.parent and preview.parent.photoMode then
            return false
        end
        return false
    end


    return scene
end

----------------------------------------------------------
-- Apply scale to model using getObjectScale()
----------------------------------------------------------
function Inspect3DPreview:applyScale()
    if not self.scene then return end

    local jo = self.scene.javaObject
    if not jo or not jo.fromLua1 then return end

    -- Asegurar que el modelo existe antes de escalar
    if (not self.valid3D) or (not self.loadedModel) then
        local okLoad = pcall(self.loadModel, self)
        if (not okLoad) or (not self.valid3D) then
            return
        end
    end

    local ok, vec = pcall(jo.fromLua1, jo, "getObjectScale", "itemModel")
    if not ok or not vec or not vec.set then return end

    local s = (self.baseScale or 1.0) * (self.scale or 1.0)
    vec:set(s)
end

----------------------------------------------------------
-- Load item model
----------------------------------------------------------
function Inspect3DPreview:loadModel()
    if not self.scene then return false end
    if not self.item then return false end

    local modelName = getModelName(self.item)
    if not modelName then
        self.valid3D = false
        return false
    end

    if self.loadedModel == modelName then
        self.valid3D = true
        return true
    end

    if self.loadedModel then
        sceneCall(self.scene, "removeModel", "itemModel")
    end

    self.loadedModel = modelName

    local created = sceneCall(self.scene, "createModel", "itemModel", modelName)
    if not created then
        self.valid3D = false
        return false
    end

    local isWeapon = (self.item.getWeaponSprite and self.item:getWeaponSprite()) ~= nil
    sceneCall(self.scene, "setModelWeaponRotationHack", "itemModel", isWeapon)

    ----------------------------------------------------------
    -- Apply initial rotation from rotation profile system
    -- (solo guardamos los angulos; no usamos setModelRotation
    --  para evitar crashes con fromLua4 en algunas builds)
    ----------------------------------------------------------
    local rot = RotationProfile.getRotation(self.item) or {}
    self.angleX = rot.angleX or 0
    self.angleY = rot.angleY or 0
    self.angleZ = rot.angleZ or 0

    -- Si el engine soporta getModelAABB se podría centrar aquí. Pero en algunas builds
    -- lanzar la llamada provoca "unhandled getModelAABB", así que se omite para evitar
    -- errores en consola. Offsets quedan en 0,0,0.

    -- Base scale by type
    local category = self.item.getCategory and self.item:getCategory() or ""
    self.baseScale = 1.8

    if isWeapon then
        self.baseScale = 2.4
    elseif category == "Clothing" then
        self.baseScale = 1.8
    elseif self.item.getModel then
        self.baseScale = 2.0
    end

    self.scale = self.scale or DEFAULT_ZOOM
    self:applyScale()

    self.valid3D = true
    self.didAutoFit = false
    self.didCenter = false
    self.baseOffsetX = 0
    self.baseOffsetY = 0
    self.baseOffsetZ = 0
    return true
end

----------------------------------------------------------
-- CENTER MODEL ON ORIGIN USING ITS BOUNDS
----------------------------------------------------------
function Inspect3DPreview:centerModelToOrigin()
    if not self.scene or not self.valid3D then return end
    if not self.modelCenterX or not self.modelCenterY or not self.modelCenterZ then return end

    local ox = -(self.modelCenterX or 0)
    local oy = -(self.modelCenterY or 0)
    local oz = -(self.modelCenterZ or 0)

    self.baseOffsetX = ox
    self.baseOffsetY = oy
    self.baseOffsetZ = oz

    -- Try offset first; fallback to position if available
    local ok = sceneCall(self.scene, "setModelOffset", "itemModel", ox, oy, oz)
    if not ok then
        sceneCall(self.scene, "setModelPosition", "itemModel", ox, oy, oz)
    end
end

function Inspect3DPreview:applyBaseOffset()
    if not self.scene or not self.valid3D then return end
    local ox = self.baseOffsetX or 0
    local oy = self.baseOffsetY or 0
    local oz = self.baseOffsetZ or 0
    local ok = sceneCall(self.scene, "setModelOffset", "itemModel", ox, oy, oz)
    if not ok then
        sceneCall(self.scene, "setModelPosition", "itemModel", ox, oy, oz)
    end
end

----------------------------------------------------------
-- AUTO CENTER + AUTO SCALE
----------------------------------------------------------
function Inspect3DPreview:autoFitModel()
    if not self.scene or not self.valid3D then return end
    if not self.scene.fromLua1 then return end

    local ok, bb = pcall(function()
        return self.scene:fromLua1("getModelAABB", "itemModel")
    end)

    if not ok or not bb or type(bb) ~= "table" then
        return
    end

    -- calcular centro del modelo
    local cx = (bb.x1 + bb.x2) * 0.5
    local cy = (bb.y1 + bb.y2) * 0.5
    local cz = (bb.z1 + bb.z2) * 0.5

    self.modelCenterX = cx
    self.modelCenterY = cy
    self.modelCenterZ = cz

    ------------------------------------------------------
    -- AUTO SCALE: que ocupe un % del viewport
    ------------------------------------------------------
    local sizeX = math.abs(bb.x2 - bb.x1)
    local sizeY = math.abs(bb.y2 - bb.y1)
    local sizeZ = math.abs(bb.z2 - bb.z1)
    local maxSize = math.max(sizeX, sizeY, sizeZ)

    if maxSize <= 0 then return end

    -- Ajusta esto a gusto. 2.2 da mejores resultados con items pequenos.
    local targetScale = AUTO_FIT_TARGET / maxSize

    self.scale = targetScale
    self:centerModelToOrigin()
    self.didCenter = true
    self:applyScale()
end

----------------------------------------------------------
-- RESET TOTAL DE VISTA
----------------------------------------------------------
function Inspect3DPreview:resetView()
    -- rotación inicial
    self.angleX = 15
    self.angleY = 120
    self.angleZ = 0

    -- escala inicial
    self.scale = DEFAULT_ZOOM

    -- ajuste por tipo (opcional)
    if self.item and self.item.getWeaponSprite and self.item:getWeaponSprite() then
        self.angleX = 0
        self.angleY = 120
        self.scale = DEFAULT_WEAPON_ZOOM
    end

    self:applyScale()
    self.didAutoFit = false
    self.didCenter = false
    self:applyBaseOffset()
end

----------------------------------------------------------
-- Render
----------------------------------------------------------
function Inspect3DPreview:render(x, y, w, h, parent)
    if not self.scene then
        local ok = pcall(self.createScene, self, x, y, w, h, parent)
        if not ok then
            self.valid3D = false
            return false
        end
        local okLoad = pcall(self.loadModel, self)
        if not okLoad then
            self.valid3D = false
            return false
        end
    end

    if not self.scene then return false end

    -- Cache box for hit-test
    self.boxX = x
    self.boxY = y
    self.boxW = w
    self.boxH = h

    self.scene:setX(x)
    self.scene:setY(y)
    self.scene:setWidth(w)
    self.scene:setHeight(h)

    if not self.valid3D then
        local okLoad = pcall(self.loadModel, self)
        if (not okLoad) or (not self.valid3D) then
            return false
        end
    end

    -- primera vez con modelo cargado: auto-fit
    if self.valid3D and not self.didAutoFit then
        self:autoFitModel()
        self.didAutoFit = true
    end

    sceneCall(self.scene, "setViewRotation",
        self.angleX or 30.0,
        self.angleY or 0.0,
        self.angleZ or 0.0
    )

    return true
end

----------------------------------------------------------
-- Hit-test over 3D area
----------------------------------------------------------
function Inspect3DPreview:isMouseOverBox()
    if not (self.boxX and self.boxY and self.boxW and self.boxH) then
        return false
    end
    local mx, my = getMouseX(), getMouseY()
    return mx >= self.boxX and mx <= (self.boxX + self.boxW)
       and my >= self.boxY and my <= (self.boxY + self.boxH)
end

----------------------------------------------------------
-- Input
----------------------------------------------------------
function Inspect3DPreview:onMouseWheel(delta)
    -- Si no hay escena/modelo válido, intenta cargarlo primero
    if (not self.valid3D) or (not self.loadedModel) then
        local okLoad = pcall(self.loadModel, self)
        if (not okLoad) or (not self.valid3D) then
            return true
        end
    end

    self.scale = self.scale or DEFAULT_ZOOM
    self.scale = self.scale - delta * 0.1
    self.scale = math.max(0.3, math.min(3.5, self.scale))
    self:applyScale()
    return true
end

function Inspect3DPreview:onMouseMove(dx, dy)
    local rightDown = isMouseButtonDown(1)
    if rightDown then
        self.angleY = (self.angleY + dx * 1.2) % 360
        self.angleX = math.max(-85, math.min(85, self.angleX - dy * 1.0))
    end
end

----------------------------------------------------------
-- DOBLE CLIC: auto-center + reset
----------------------------------------------------------
function Inspect3DPreview:onMouseDoubleClick(x, y)
    if not self:isMouseOverBox() then return end

    if isMouseButtonDown(0) then
        self:resetView()
        self:autoFitModel()
        -- Reaplicar centrado si autoFit no lo hizo (fallback)
        if not self.didCenter then
            self:centerModelToOrigin()
            self.didCenter = true
        end
        self:applyBaseOffset()
    end
end

----------------------------------------------------------
-- Destroy
----------------------------------------------------------
function Inspect3DPreview:destroy()
    if self.scene then
        self.scene:removeFromUIManager()
        self.scene = nil
    end
end

return Inspect3DPreview
