ContourBuilder = {}

local NAV_MESH_NAME_PREFIX = "PastureHerding_navMesh_"
local POSITIONING_NAME_PREFIX = "PastureHerding_positioning_"

-- Vanilla cow barns tag their navmeshBlockingNode shape with
-- collisionFilterGroup=0x800000. CollisionMask.ANIMAL_POSITIONING happens to
-- resolve to a different value (0x400100) in this build; both may matter,
-- so we apply the literal vanilla bit and also try the constant.
local VANILLA_ANIMAL_POSITIONING_GROUP = 0x800000
local COLLISION_MASK_ALL = 0xFFFFFFFF

ContourBuilder.DEFAULTS = {
    cellSize = 0.3,
    cellHeight = 0.2,
    agentHeight = 1.6,
    agentRadius = 0.4,
    agentMaxClimb = 0.4,
    agentMaxSlope = 45,
    minRegionSize = 8,
    mergeRegionSize = 20,
    maxEdgeLength = 12,
    maxSimplificationError = 1.3,
}

local seq = 0

local function nextSeq()
    seq = seq + 1
    return seq
end

function ContourBuilder.buildSquareAround3D(cx, cy, cz, halfSize)
    halfSize = halfSize or 5.0
    return {
        cx - halfSize, cy, cz - halfSize,
        cx + halfSize, cy, cz - halfSize,
        cx + halfSize, cy, cz + halfSize,
        cx - halfSize, cy, cz + halfSize,
    }
end

-- NOTE: an earlier ContourBuilder.fabricateFromShapes() built the nav mesh from
-- a runtime plane shape, but a runtime shape has no CPU-accessible mesh, so the
-- engine's buildNavMesh always rejected it ("missing CPU mesh flag" / "no
-- triangles collected") and we fell back to the contour path below anyway — for
-- two engine warnings per pasture and no benefit. It was removed; fabricate() is
-- the sole path.
function ContourBuilder.fabricate(contour3D, collisionMask)
    if contour3D == nil or #contour3D < 9 or (#contour3D % 3) ~= 0 then
        PastureHerdingLog.error("ContourBuilder.fabricate: contour invalid (got %d numbers, need multiple of 3 and >= 9)",
            contour3D and #contour3D or 0)
        return nil
    end
    if createNavMesh == nil or buildNavMeshFromContour == nil then
        PastureHerdingLog.error("ContourBuilder.fabricate: engine NavMesh APIs missing")
        return nil
    end

    local terrainId = g_currentMission and g_currentMission.terrainRootNode or 0
    if terrainId == 0 then
        PastureHerdingLog.warning("ContourBuilder.fabricate: terrainRootNode is 0; build may fail")
    end
    local mask = collisionMask or 0xFFFFFFFF

    local n = nextSeq()
    local name = NAV_MESH_NAME_PREFIX .. n
    local meshId = createNavMesh(name)
    if meshId == nil or meshId == 0 then
        PastureHerdingLog.error("ContourBuilder.fabricate: createNavMesh returned %s", tostring(meshId))
        return nil
    end

    local d = ContourBuilder.DEFAULTS
    local okBuild, errOrResult = pcall(buildNavMeshFromContour,
        meshId, contour3D, terrainId, mask,
        d.cellSize, d.cellHeight,
        d.agentHeight, d.agentRadius, d.agentMaxClimb, d.agentMaxSlope,
        d.minRegionSize, d.mergeRegionSize,
        d.maxEdgeLength, d.maxSimplificationError)

    if not okBuild then
        PastureHerdingLog.error("ContourBuilder.fabricate: buildNavMeshFromContour raised: %s", tostring(errOrResult))
        if delete ~= nil then delete(meshId) end
        return nil
    end
    if errOrResult == false then
        PastureHerdingLog.error("ContourBuilder.fabricate: buildNavMeshFromContour returned false (contour=%d points, terrain=%s, mask=0x%X)",
            #contour3D / 3, tostring(terrainId), mask)
        if delete ~= nil then delete(meshId) end
        return nil
    end

    local area = "?"
    if getNavMeshSurfaceArea ~= nil then
        local okA, a = pcall(getNavMeshSurfaceArea, meshId)
        if okA then area = tostring(a) end
    end
    PastureHerdingLog.dev("ContourBuilder.fabricate: navMesh '%s' meshId=%s built from %d-point contour (surfaceArea=%s)",
        name, tostring(meshId), #contour3D / 3, area)

    -- Companion ANIMAL_POSITIONING shape. Average Y of contour gives the
    -- plane's flat height (terrain may roll but a single plane is enough
    -- for the engine's raycast probe).
    local positioning = ContourBuilder._buildPositioningPlane(contour3D, n)

    return meshId, name, positioning
end

function ContourBuilder._buildPositioningPlane(contour3D, seqN)
    if createPlaneShapeFrom2DContour == nil or createTransformGroup == nil or link == nil then
        PastureHerdingLog.warning("ContourBuilder: positioning-plane APIs missing; skipping companion shape")
        return nil
    end

    local nPts = #contour3D / 3
    local flat2D = {}
    local sumY = 0
    for i = 1, #contour3D, 3 do
        flat2D[#flat2D + 1] = contour3D[i]       -- x
        flat2D[#flat2D + 1] = contour3D[i + 2]   -- z
        sumY = sumY + contour3D[i + 1]
    end
    local avgY = sumY / nPts

    local tgName = POSITIONING_NAME_PREFIX .. seqN .. "_tg"
    local tg = createTransformGroup(tgName)
    if tg == nil or tg == 0 then
        PastureHerdingLog.warning("ContourBuilder: createTransformGroup failed for positioning plane")
        return nil
    end
    if setTranslation ~= nil then setTranslation(tg, 0, avgY, 0) end

    local shapeName = POSITIONING_NAME_PREFIX .. seqN .. "_shape"
    local okShape, shape = pcall(createPlaneShapeFrom2DContour, shapeName, flat2D, true)
    if not okShape or shape == nil or shape == 0 then
        PastureHerdingLog.warning("ContourBuilder: createPlaneShapeFrom2DContour failed: %s", tostring(shape))
        if delete ~= nil then delete(tg) end
        return nil
    end

    link(tg, shape)

    -- Probe which collision-tagging APIs exist on this FS25 build.
    local tries = {
        { name = "setCollisionFilterGroup", fn = setCollisionFilterGroup, arg = VANILLA_ANIMAL_POSITIONING_GROUP },
        { name = "setCollisionMask",        fn = setCollisionMask,        arg = VANILLA_ANIMAL_POSITIONING_GROUP },
    }
    local appliedGroup = "nil"
    for _, t in ipairs(tries) do
        if t.fn ~= nil then
            local ok, err = pcall(t.fn, shape, t.arg)
            if ok then
                appliedGroup = string.format("%s(0x%X)", t.name, t.arg)
            else
                PastureHerdingLog.warning("ContourBuilder: %s raised: %s", t.name, tostring(err))
            end
        end
    end

    local maskTries = {
        { name = "setCollisionFilterMask", fn = setCollisionFilterMask, arg = COLLISION_MASK_ALL },
    }
    local appliedMask = "nil"
    for _, t in ipairs(maskTries) do
        if t.fn ~= nil then
            local ok, err = pcall(t.fn, shape, t.arg)
            if ok then
                appliedMask = string.format("%s(0x%X)", t.name, t.arg)
            else
                PastureHerdingLog.warning("ContourBuilder: %s raised: %s", t.name, tostring(err))
            end
        end
    end

    if setVisibility ~= nil then pcall(setVisibility, shape, false) end
    if setRigidBodyType ~= nil then pcall(setRigidBodyType, shape, RigidBodyType and RigidBodyType.STATIC or 1) end

    PastureHerdingLog.dev("ContourBuilder: positioning plane built (tg=%s shape=%s avgY=%.2f group=%s mask=%s)",
        tostring(tg), tostring(shape), avgY, appliedGroup, appliedMask)
    return { tg = tg, shape = shape }
end

function ContourBuilder.delete(meshId)
    if meshId == nil or meshId == 0 then return end
    if delete ~= nil then delete(meshId) end
end

function ContourBuilder.deletePositioning(positioning)
    if positioning == nil then return end
    if delete ~= nil then
        if positioning.shape ~= nil and positioning.shape ~= 0 then delete(positioning.shape) end
        if positioning.tg ~= nil and positioning.tg ~= 0 then delete(positioning.tg) end
    end
end
