import React from "react";
import OrbitCameraControls from "3dModels/CameraControls/OrbitCameraControls";
import CameraOverlay from "3dModels/CameraControls/CameraOverlay";
import { Canvas } from "@react-three/fiber";
import { Bin } from "models";
import BinShell from "../BinParts/BinShell";
import GrainFillFlat from "../Systems/Inventory/GrainFillFlat";
import BinCables from "../Systems/Cables/BinCables";
import { Vector3 } from "three";
import { useMemo } from "react";
import { BuildCableData, CableData } from "../Data/BuildCableData";
import { BuildNodeData, NodeData } from "../Data/BuildNodeData";
import { pond } from "protobuf-ts/pond";
import GrainCableFill from "../Systems/Inventory/GrainCableFill";
import TempHeatMap from "../Systems/Heatmap/TempHeatMap";
import NodePointCloud from "../Systems/Heatmap/NodePointCloud";
import { Box } from "@mui/material";
import MoistureHeatMap from "../Systems/Heatmap/MoistureHeatmap";
import { grainYBoundsFromFillPercent } from "../utils/grainFillBounds";
import { RadiansToDegrees } from "common/TrigFunctions";
// import { GrainCable } from "models/GrainCable"

// Fallback dimensions used when a bin has no advanced dimensions configured.
// Chosen to resemble a typical mid-size grain bin.
const DEFAULT_ROOF_HEIGHT_CM     = 200; //  2 m
const DEFAULT_FLAT_HEIGHT_CM   =   0; // flat bottom
const DEFAULT_HOPPER_HEIGHT_CM   =   280; // hopper bottom

interface Props {
    bin: Bin
    scale: number
    /**
     * cables can come from the bins status when not passed in, however data may be out of synce from when the cables read to when the bins status updates
     * they may need to be passed in but for the moment we wont worry about it due to the last active on the bin page can be used to explain being out of sync
     * if it becomes an issue I can make the changes necessary to pass the cable components in
     */
    // cables?: GrainCable[]
    fillPercent?: number
    nodeClick?: (node: NodeData, cable: CableData) => void
    showGrain?: boolean
    showTempHeatmap?: boolean
    showMoistureHeatmap?: boolean
    showHotspots?: boolean
    /**
     * When true renders a reset camera button overlaid on the 3D view.
     */
    showResetButton?: boolean
    showTemp?: boolean
    showMoisture?: boolean
    xOffset?: number
}

export default function Bin3dView(props: Props) {
    const { bin, scale = 100, fillPercent, nodeClick, showTempHeatmap, showGrain, showHotspots, showResetButton, showMoistureHeatmap, showTemp, showMoisture, xOffset } = props

    // Resolve effective dimensions, falling back to defaults when advanced
    // dimensions have not been configured (bin methods return 0 in that case).
    const dims = useMemo(() => ({
        diameter:       bin.diameter(),
        sidewallHeight: bin.sidewallHeight() || bin.diameter() + 50, //calculated using the diameter so 'default' bins always have the same shape
        roofHeight:     bin.roofHeight()     || DEFAULT_ROOF_HEIGHT_CM,
        hopperHeight:   bin.hopperHeight()   || (bin.binShape() === pond.BinShape.BIN_SHAPE_HOPPER_BOTTOM ? DEFAULT_HOPPER_HEIGHT_CM : DEFAULT_FLAT_HEIGHT_CM),
    }), [bin]);

    // Compute the initial camera radius so the full bin fits in frame.
    // Uses the perspective camera FOV (default 75°) with a padding factor.
    // Both the diameter and total height are considered so neither is clipped.
    const initialCameraRadius = useMemo(() => {
        const totalHeight = (dims.sidewallHeight + dims.roofHeight + dims.hopperHeight) / scale;
        const diameter    = dims.diameter / scale;
        const largestDim  = Math.max(totalHeight, diameter);
        const fovRad      = (75 * Math.PI) / 180;
        const padding     = 1.3; // 30% breathing room
        return (largestDim / (2 * Math.tan(fovRad / 2))) * padding;
    }, [dims, scale]);

    const cableData = useMemo(() => BuildCableData(bin, dims), [bin, dims]);
    const nodeData  = useMemo(() => BuildNodeData(cableData), [cableData]);

    const isCableInventory =
        bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_AUTOMATIC ||
        bin.inventoryControl() === pond.BinInventoryControl.BIN_INVENTORY_CONTROL_HYBRID_CABLE;

    // For cable inventory, TempHeatMap derives the wavy surface directly from
    // the top nodes in nodeData — no scalar needed.
    //
    // Flat fill: Y bounds from the same volume math as GrainFillFlat.
    const flatGrainBounds = useMemo(() => {
        if (isCableInventory || fillPercent === undefined) return undefined;
        return grainYBoundsFromFillPercent(
            dims.diameter,
            dims.sidewallHeight,
            dims.hopperHeight,
            fillPercent
        );
    }, [isCableInventory, fillPercent, dims]);

    // Internal ref — wired to OrbitCameraControls and called by CameraOverlay
    const resetCameraFn = React.useRef<(() => void) | null>(null);

    const grainInventory = () => {
        if (isCableInventory) {
            return (
                <GrainCableFill
                    diameter={dims.diameter}
                    nodes={nodeData}
                    sidewallHeight={dims.sidewallHeight}
                    fallbackFillPercent={fillPercent}
                    hopperHeight={dims.hopperHeight}
                    grainOpacity={0.3}
                />
            )
        } else if (fillPercent) {
            return (
                <GrainFillFlat
                    diameter={dims.diameter}
                    sidewallHeight={dims.sidewallHeight}
                    hopperHeight={dims.hopperHeight}
                    fillPercent={fillPercent}
                    grainOpacity={0.3}
                />
            )
        }
    }

    return (
        <Canvas style={{position: "absolute", inset: 0}}>
            <OrbitCameraControls
                clampVerticalRotation
                initialRadius={initialCameraRadius}
                maxRadius={initialCameraRadius * 2}
                onReset={fn => { resetCameraFn.current = fn; }}
                viewOffset={xOffset}
            />
            <group scale={[1 / scale, 1 / scale, 1 / scale]}>
                <BinShell
                    binBodyColour="#fff"
                    radialSegments={20}
                    binMetalness={0.5}
                    binRoughness={0.7}
                    binOpacity={0.2}
                    diameter={dims.diameter}
                    sidewallHeight={dims.sidewallHeight}
                    roofHeight={dims.roofHeight}
                    hopperHeight={dims.hopperHeight}
                    renderOrder={4}
                />

                {showGrain && grainInventory()}

                <BinCables
                    displayTemp={showTemp}
                    displayMoisture={showMoisture}
                    cableData={cableData}
                    nodeData={nodeData}
                    bin={bin}
                    onNodeClick={(node, cable) => {
                        if (nodeClick) nodeClick(node, cable)
                    }}
                    renderOrder={5}
                />

                {showHotspots && <NodePointCloud bin={bin} nodes={nodeData} />}

                {/* note that the heatmaps internally use render order 1,2, and 3 for the three separate coloured meshes */}
                {showTempHeatmap && (
                    <TempHeatMap
                        binDimensions={dims}
                        targetTemp={bin.targetTemp()}
                        nodes={nodeData}
                        flatMaxY={flatGrainBounds?.maxY}
                        flatMinY={flatGrainBounds?.minY}
                    />
                )}
                {showMoistureHeatmap && (
                    <MoistureHeatMap
                        binDimensions={dims}
                        targetMoisture={bin.targetMoisture()}
                        nodes={nodeData}
                        flatMaxY={flatGrainBounds?.maxY}
                        flatMinY={flatGrainBounds?.minY}
                    />
                )}

            </group>

            <ambientLight intensity={0.2} color={"white"} />
            <directionalLight intensity={0.2} color={"white"} position={[0, 0, 5]} />
            <directionalLight intensity={0.2} color={"white"} position={[0, 0, -5]} />
            <directionalLight intensity={0.2} color={"white"} position={[5, 0, 0]} />
            <directionalLight intensity={0.2} color={"white"} position={[-5, 0, 0]} />

            {/* Overlay — must be inside <Canvas> so it shares the same stacking context */}
            {showResetButton && (
                <CameraOverlay onReset={() => resetCameraFn.current?.()} />
            )}
        </Canvas>
    )
}
