--# CubeMapSphere
CubeMapSphere = class()

function CubeMapSphere:init(scene, radius, textureSize)
    self.scene = scene
    self.radius = radius
    self.textureSize = textureSize
    self.drawingColor = color(255, 255, 255)
    self.brushSize = 20
    self.needsTextureUpdate = false
    self.lastTextureUpdate = 0
    self.textureUpdateInterval = 0.05 -- Update texture every 100ms
    
    self:setupCubeMap()
    self:setupSphere()
end

function CubeMapSphere:setupCubeMap()
    local faceColors = {
        color(255, 64, 64),   -- +X (right)
        color(64, 64, 255),   -- -X (left)
        color(64, 255, 64),   -- +Y (top)
        color(255, 255, 64),  -- -Y (bottom)
        color(64, 255, 255),  -- +Z (front)
        color(255, 64, 255)   -- -Z (back)
    }
    
    local faceLabels = {
        "+X (Right)", "-X (Left)", "+Y (Top)", 
        "-Y (Bottom)", "+Z (Front)", "-Z (Back)"
    }
    
    self.cubeImages = {}
    for i = 1, 6 do
        local img = image(self.textureSize, self.textureSize)
        setContext(img)
        background(faceColors[i])
        fill(255, 100)
        text(faceLabels[i], self.textureSize/2, self.textureSize/2)
        setContext()
        self.cubeImages[i] = img
    end
    
    self.cubeTexture = craft.cubeTexture(self.cubeImages)
end

function CubeMapSphere:setupSphere()
    self.entity = self.scene:entity()
    self.entity.position = vec3(0, 0, 0)
    
    local sphereModel = craft.model.icosphere(self.radius, 4)
    self.renderer = self.entity:add(craft.renderer, sphereModel)
    
    -- Use StandardSphere material as requested
    local material = craft.material(asset.StandardSphere)
    material.map = self.cubeTexture
    material.roughness = 0.5
    material.metalness = 0.0
    self.renderer.material = material
end

function CubeMapSphere:handleTouch(t)
    if t.state == BEGAN or t.state == MOVING then
        -- Get the camera component, not the camera entity
        local cameraComponent = self.scene.camera:get(craft.camera)
        local rayOrigin, rayDir = cameraComponent:screenToRay(vec2(t.x, t.y))
        local intersect = self:intersectRaySphere(rayOrigin, rayDir, self.entity.position, self.radius)
        
        if intersect then
            local faceIndex, u, v = self:sphereToCubemapFace(intersect - self.entity.position)
            if faceIndex then
                self:drawOnFace(faceIndex, u, v)
                return true
            end
        end
    end
    return false
end

function CubeMapSphere:intersectRaySphere(rayOrigin, rayDir, sphereCenter, sphereRadius)
    local oc = rayOrigin - sphereCenter
    local a = rayDir:dot(rayDir)
    local b = 2 * oc:dot(rayDir)
    local c = oc:dot(oc) - sphereRadius * sphereRadius
    local discriminant = b * b - 4 * a * c
    
    if discriminant < 0 then return nil end
    
    local t = (-b - math.sqrt(discriminant)) / (2 * a)
    if t < 0 then return nil end
    
    return rayOrigin + rayDir * t
end

function CubeMapSphere:sphereToCubemapFace(p)
    local absX, absY, absZ = math.abs(p.x), math.abs(p.y), math.abs(p.z)
    local faceIndex, u, v
    
    if absX >= absY and absX >= absZ then
        if p.x > 0 then
            faceIndex = 1  -- +X
            u = -p.z / absX
            v = -p.y / absX
        else
            faceIndex = 2  -- -X
            u = p.z / absX
            v = -p.y / absX
        end
    elseif absY >= absX and absY >= absZ then
        if p.y > 0 then
            faceIndex = 3  -- +Y
            u = p.x / absY
            v = p.z / absY
        else
            faceIndex = 4  -- -Y
            u = p.x / absY
            v = -p.z / absY
        end
    else
        if p.z > 0 then
            faceIndex = 5  -- +Z
            u = p.x / absZ
            v = -p.y / absZ
        else
            faceIndex = 6  -- -Z
            u = -p.x / absZ
            v = -p.y / absZ
        end
    end
    
    u = (u + 1) / 2 * self.textureSize
    v = (v + 1) / 2 * self.textureSize
    
    return faceIndex, u, v
end

function CubeMapSphere:drawOnFace(faceIndex, u, v)
    -- u,v are 0..textureSize (floats) from sphereToCubemapFace
    local r = self.brushSize * 0.5
    local col = self.drawingColor
    self:drawDiskCrossFace(faceIndex, u, v, r, col)
    self.needsTextureUpdate = true
end

function CubeMapSphere:updateCubeTexture()
    -- Only update if enough time has passed since last update
    if ElapsedTime - self.lastTextureUpdate > self.textureUpdateInterval then
        self.cubeTexture = craft.cubeTexture(self.cubeImages)
        self.renderer.material.map = self.cubeTexture
        self.lastTextureUpdate = ElapsedTime
        collectgarbage()
    end
end

function CubeMapSphere:drawPreview()
    pushStyle()
    spriteMode(CORNER)
    noSmooth()
    
    local s = WIDTH/4
    local ySetting = HEIGHT/4 - s/2
    local pos = {
        {x = s*2, y = ySetting},      -- 1 (+X)
        {x = 0,   y = ySetting},      -- 2 (-X)
        {x = s,   y = ySetting - s},  -- 3 (+Y)
        {x = s,   y = ySetting + s},  -- 4 (-Y)
        {x = s,   y = ySetting},      -- 5 (+Z)
        {x = s*3, y = ySetting}       -- 6 (-Z)
    }
    
    for i = 1, 6 do
        sprite(self.cubeImages[i], pos[i].x, pos[i].y, s, s)
    end
    
    popStyle()
end

function CubeMapSphere:setDrawingColor(color)
    self.drawingColor = color
end

function CubeMapSphere:setBrushSize(size)
    self.brushSize = size
end

function CubeMapSphere:clearAllFaces()
    local faceColors = {
        color(255, 64, 64),   -- +X
        color(64, 64, 255),   -- -X
        color(64, 255, 64),   -- +Y
        color(255, 255, 64),  -- -Y
        color(64, 255, 255),  -- +Z
        color(255, 64, 255)   -- -Z
    }
    
    for i = 1, 6 do
        setContext(self.cubeImages[i])
        background(faceColors[i])
        fill(255, 100)
        text("Face "..i, self.textureSize/2, self.textureSize/2)
        setContext()
    end
    
    self:updateCubeTexture()
end

-- Face frames that match your sphereToCubemapFace mapping
local FACE_BASIS = {
    [1]={n=vec3( 1,0,0), u=vec3( 0,0,-1), v=vec3( 0,-1, 0)}, -- +X
    [2]={n=vec3(-1,0,0), u=vec3( 0,0, 1), v=vec3( 0,-1, 0)}, -- -X
    [3]={n=vec3( 0,1,0), u=vec3( 1,0, 0), v=vec3( 0, 0, 1)}, -- +Y
    [4]={n=vec3( 0,-1,0),u=vec3( 1,0, 0), v=vec3( 0, 0,-1)}, -- -Y
    [5]={n=vec3( 0,0,1), u=vec3( 1,0, 0), v=vec3( 0,-1, 0)}, -- +Z
    [6]={n=vec3( 0,0,-1),u=vec3(-1,0, 0), v=vec3( 0,-1, 0)}, -- -Z
}

local function faceUVToDir(face, u, v)
    local b = FACE_BASIS[face]
    return (b.n + b.u*u + b.v*v):normalize()
end

-- direction -> (face, u, v) with u,v in [-1,1]
local function dirToFaceUV(d)
    local ax, ay, az = math.abs(d.x), math.abs(d.y), math.abs(d.z)
    if ax >= ay and ax >= az then
        local m=ax
        if d.x>0 then return 1, -d.z/m, -d.y/m end
        return 2,  d.z/m, -d.y/m
    elseif ay >= ax and ay >= az then
        local m=ay
        if d.y>0 then return 3,  d.x/m,  d.z/m end
        return 4,  d.x/m, -d.z/m
    else
        local m=az
        if d.z>0 then return 5,  d.x/m, -d.y/m end
        return 6, -d.x/m, -d.y/m
    end
end

-- direction -> (face, x, y) as 1-based pixel coords for your texture size
local function dirToFacePixel(d, W, H)
    local f,u,v = dirToFaceUV(d)
    local eps = 1e-7
    if u<=-1 then u=-1+eps elseif u>=1 then u=1-eps end
    if v<=-1 then v=-1+eps elseif v>=1 then v=1-eps end
    local x = math.floor((u+1)*0.5*W) + 1
    local y = math.floor((v+1)*0.5*H) + 1
    if x<1 then x=1 elseif x>W then x=W end
    if y<1 then y=1 elseif y>H then y=H end
    return f,x,y
end

-- Route a possibly out-of-range pixel on `face` to the correct neighbor face/pixel (1-based out)
local function routePixel(face, px0, py0, W, H)
    -- inputs px0,py0 are 0-based *math* pixel indices (can be <0 or >=W/H)
    if px0 >= 0 and px0 < W and py0 >= 0 and py0 < H then
        return face, px0+1, py0+1
    end
    -- convert pixel center to uv in [-1,1], then reproject
    local u = ((px0 + 0.5) / W) * 2 - 1
    local v = ((py0 + 0.5) / H) * 2 - 1
    local dir = faceUVToDir(face, u, v)
    return dirToFacePixel(dir, W, H)
end

-- Draw a solid disk across faces (no falloff). `cx,cy` are in 0..W space (as returned by sphereToCubemapFace)
function CubeMapSphere:drawDiskCrossFace(face, cx, cy, radiusPx, col)
    local W, H = self.textureSize, self.textureSize
    -- integer bounds (unclamped), 0-based
    local minx = math.floor(cx - radiusPx)
    local maxx = math.ceil (cx + radiusPx)
    local miny = math.floor(cy - radiusPx)
    local maxy = math.ceil (cy + radiusPx)
    
    for py = miny, maxy do
        for px = minx, maxx do
            local dx = (px + 0.5) - (cx + 0.5)
            local dy = (py + 0.5) - (cy + 0.5)
            if dx*dx + dy*dy <= radiusPx*radiusPx then
                local f2, qx, qy = routePixel(face, px, py, W, H)  -- 1-based outputs
                local img = self.cubeImages[f2]
                img:set(qx, qy, col)
            end
        end
    end
end