import React, { useRef } from "react"
import {
  Cylinder, 
  Html, 
  PerspectiveCamera, 
  useGLTF,
} from "@react-three/drei"
import { 
  // Canvas, 
  ThreeEvent, 
  useFrame 
} from "@react-three/fiber"
import { GLTF } from "three-stdlib"
import {
  pinBindingLabel, BoxTopBaseProps, PinBindingListItems, setPinBinding,
  findBinding, triggerBinding, t, isExpress, ButtonPin,
} from "./stub"
import { BufferGeometry } from "three"
import { debounce, some } from "lodash"
import "./styles/box.css"
import { BindingTargetDropdown } from "./stub_components"
// @ts-expect-error
import box from "~/public/assets/farmbot/3D/models/box.glb"
// @ts-expect-error
import btn from "~/public/assets/farmbot/3D/models/push_button.glb"
// @ts-expect-error
import led from "~/public/assets/farmbot/3D/models/led_indicator.glb"

import * as THREE from "three"

// const DRACO_LIB_DIR = "../farmbot-3d-demo-react~/public/assets/farmbot/3D/lib/"
const DRACO_LIB_DIR = "~/public/assets/farmbot/3D/lib/"
// const DRACO_LIB_DIR = "/3D/lib/"
// const DRACO_LIB_DIR = "/draco/"

const MODELS = {
  box,
  btn,
  led,
}

type Box = GLTF & {
  nodes: {
    Electronics_Box: THREE.Mesh
    Electronics_Box_Gasket: THREE.Mesh
    Electronics_Box_Lid: THREE.Mesh
  }
  materials: {
    [Material.box]: THREE.MeshStandardMaterial
    [Material.gasket]: THREE.MeshStandardMaterial
    [Material.lid]: THREE.MeshStandardMaterial
  }
}

export enum Material {
  box = "0.901961_0.901961_0.901961_0.000000_0.000000",
  gasket = "0.301961_0.301961_0.301961_0.000000_0.000000",
  lid = "0.564706_0.811765_0.945098_0.000000_0.623529",
  button = "0.701961_0.701961_0.701961_0.000000_0.000000",
  led = "0.600000_0.600000_0.600000_0.000000_0.000000",
}

type Btn = GLTF & {
  nodes: {
    ["Push_Button_-_Red"]: THREE.Mesh
  }
  materials: {
    [Material.button]: THREE.MeshStandardMaterial
  }
}

type Led = GLTF & {
  nodes: {
    LED: THREE.Mesh
  }
  materials: {
    [Material.led]: THREE.MeshStandardMaterial
  }
}

Object.values(MODELS).map(model => useGLTF.preload(model, DRACO_LIB_DIR))

type MeshObject = THREE.Mesh<BufferGeometry, THREE.MeshStandardMaterial>

const Z = 131
export namespace IColor {
  export enum estop {
    on = 0xef4037,
    off = 0xd89a97,
  }
  export enum unlock {
    on = 0xf5e909,
    off = 0xe1de94,
  }
  export enum connect {
    on = 0x1073e0,
    off = 0x88a4c3,
  }
  export enum sync {
    on = 0x62c020,
    off = 0x94b87b,
  }
  export enum blank {
    on = 0xffffff,
    off = 0xf4f4f4,
  }
}

const changeItemsInGroup = (
  meshObject: MeshObject,
  cb: (x: MeshObject) => void,
  items = ["button-center", "button-color"],
) => {
  meshObject.children.map(child => {
    const object = child as MeshObject
    if (some(items.map(item => child.name.includes(item)))) {
      cb(object)
    }
    changeItemsInGroup(object, cb, items)
  })
}

export const setZForAllInGroup = (e: ThreeEvent<PointerEvent>, z: number) => {
  changeItemsInGroup(
    e.object.parent as MeshObject,
    x => x.position.z = z)
}

interface ButtonOrLedItem {
  label: string
  pinNumber: number
  blink?: boolean
  on?: boolean
  position: number
  color: { on: number, off: number }
  ref?: React.MutableRefObject<MeshObject | null>
}

export const ElectronicsBox = (props: BoxTopBaseProps) => {
  const box = useGLTF(MODELS.box, DRACO_LIB_DIR) as Box
  const btn = useGLTF(MODELS.btn, DRACO_LIB_DIR) as Btn
  const led = useGLTF(MODELS.led, DRACO_LIB_DIR) as Led
  const SCALE = 1000

  const syncLed = useRef<MeshObject>(null)
  const connLed = useRef<MeshObject>(null)
  const unlock = useRef<MeshObject>(null)
  const estop = useRef<MeshObject>(null)

  const {
    locked, sync_status,
  } = props.bot.hardware.informational_settings
  const findPinBinding = findBinding(props.resources)
  const clickBinding = triggerBinding(props.resources, props.botOnline)
  const express = isExpress(props.firmwareHardware)

  const BUTTONS: ButtonOrLedItem[] = [
    {
      label: t("Peripheral: Lights"),
      pinNumber: ButtonPin.estop,
      on: props.botOnline && !locked,
      position: -60,
      color: {
        on: IColor.estop.on,
        off: IColor.estop.off,
      },
      ref: estop,
    },
    {
      label: t("Peripheral: Camera"),
      pinNumber: ButtonPin.unlock,
      blink: props.botOnline && locked,
      position: -30,
      color: {
        on: IColor.unlock.on,
        off: IColor.unlock.off,
      },
      ref: unlock,
    },
    {
      label: t("Action: 0"),
      pinNumber: ButtonPin.btn3,
      position: 0,
      color: {
        on: IColor.blank.on,
        off: IColor.blank.off,
      },
    },
    {
      label: t("Action: 1"),
      pinNumber: ButtonPin.btn4,
      position: 30,
      color: {
        on: IColor.blank.on,
        off: IColor.blank.off,
      },
    },
    {
      label: t("Action: 2"),
      pinNumber: ButtonPin.btn5,
      position: 60,
      color: {
        on: IColor.blank.on,
        off: IColor.blank.off,
      },
    },
  ]

  const LEDS: ButtonOrLedItem[] = [
    {
      label: t("Sync"),
      pinNumber: -1,
      on: sync_status == "synced",
      blink: sync_status == "syncing",
      position: -45,
      color: {
        on: IColor.sync.on,
        off: IColor.sync.off,
      },
      ref: syncLed,
    },
    {
      label: t("Connectivity"),
      pinNumber: -1,
      on: props.botOnline,
      position: -15,
      color: {
        on: IColor.connect.on,
        off: IColor.connect.off,
      },
      ref: connLed,
    },
    {
      label: t("LED 3"),
      pinNumber: -1,
      position: 15,
      color: {
        on: IColor.blank.on,
        off: IColor.blank.off,
      },
    },
    {
      label: t("LED 4"),
      pinNumber: -1,
      position: 45,
      color: {
        on: IColor.blank.on,
        off: IColor.blank.off,
      },
    },
  ]

  useFrame((state) => {
    const t = state.clock.getElapsedTime()
    BUTTONS.concat(LEDS).map(item => {
      // [MM] troubleshooting -- causes error 'current.material.color.set is not a function'
      // [MM] probably because of other error '404 looking for /.glb'
      // const current = item.ref?.current
      // const { on, off } = item.color
      // if (current) {
      //   if (item.blink) {
      //     current.material.color.set(t % 2 < 1 ? on : off)
      //   } else {
      //     current.material.color.set(item.on ? on : off)
      //   }
      // }
    })
  })

  const getLabel = (binding: PinBindingListItems | undefined) => {
    return pinBindingLabel({
      resources: props.resources,
      sequenceIdInput: binding?.sequence_id,
      specialActionInput: binding?.special_action,
    })?.label
  }

  const [hovered, setHovered] = React.useState<number | undefined>()
  const leave = (e: ThreeEvent<PointerEvent>) => {
    setHovered(undefined)
    setZForAllInGroup(e, Z)
    document.body.style.cursor = "default"
  }
  return <group rotation={[0, 0, Math.PI / 2]}>
    <mesh name={"electronicsBox"}
      geometry={box.nodes.Electronics_Box.geometry}
      material={box.materials[Material.box]}
      scale={SCALE}
      material-color={0xffffff}
      material-emissive={0x999999} />
    <mesh name={"electronicsBoxGasket"}
      geometry={box.nodes.Electronics_Box_Gasket.geometry}
      material={box.materials[Material.gasket]}
      scale={SCALE} />
    <mesh name={"electronicsBoxLid"}
      geometry={box.nodes.Electronics_Box_Lid.geometry}
      material={box.materials[Material.lid]}
      scale={SCALE} />
    {BUTTONS
      .filter((_, index) => express ? index == 0 : true)
      .map(button => {
        const { position, color, ref, label, pinNumber } = button
        const btnPosition = express ? 0 : position
        const binding = findPinBinding(pinNumber)
        const isHovered = hovered == pinNumber
        const click = debounce(clickBinding(pinNumber))
        const setCursor = () =>
          document.body.style.cursor = binding ? "pointer" : "not-allowed"
        const enter = () => {
          !props.isEditing && setHovered(pinNumber)
          setCursor()
        }
        return <group key={btnPosition} name={"button-group"}
          onPointerUp={leave}>
          <mesh name={"button-housing"}
            geometry={btn.nodes["Push_Button_-_Red"].geometry}
            material={btn.materials[Material.button]}
            position={[-30, btnPosition, Z]}
            scale={SCALE}
            material-color={0xcccccc} />
          <group name={"action-group"}
            onPointerOver={enter}
            onPointerMove={setCursor}
            onClick={setCursor}
            onPointerOut={leave}
            onPointerDown={e => {
              if (!props.isEditing) {
                setZForAllInGroup(e, Z - 3)
                click()
              }
            }}>
            <Cylinder ref={ref}
              name={"button-color"}
              material-color={color}
              args={[9, 0, 3.5]}
              position={[-30, btnPosition, Z]}
              rotation={[Math.PI / 2, 0, 0]} />
            <Cylinder name={"button-center"}
              material-color={(binding && isHovered) ? 0xdddddd : 0xcccccc}
              args={[6.75, 0, 4]}
              position={[-30, btnPosition, Z]}
              rotation={[Math.PI / 2, 0, 0]} />
            {props.showLabels &&
              <Html name={"label"}
                center={true}
                position={[-7, btnPosition, Z]}>
                {props.isEditing
                  ? <BindingTargetDropdown key={btnPosition}
                    change={setPinBinding({
                      binding,
                      dispatch: props.dispatch,
                      resources: props.resources,
                      pinNumber: pinNumber,
                    })}
                    resources={props.resources}
                    sequenceIdInput={binding?.sequence_id}
                    specialActionInput={binding?.special_action} />
                  : <p className={[
                    "btn-label",
                    isHovered ? "hovered" : "",
                    binding ? "" : "unbound",
                  ].join(" ")}>
                    {getLabel(binding) || label}
                  </p>}
              </Html>}
          </group>
        </group>
      })}
    {LEDS
      .filter(() => !express)
      .map(ledIndicator => {
        const { position, color, ref } = ledIndicator
        return <group key={position}>
          <mesh name={"led-housing"}
            geometry={led.nodes.LED.geometry}
            material={led.materials[Material.led]}
            position={[-50, position, Z]}
            material-color={0xcccccc}
            scale={SCALE} />
          <Cylinder ref={ref} name={"led-color"}
            material-color={color}
            args={[6.75, 6.75, 3]}
            position={[-50, position, Z]}
            rotation={[Math.PI / 2, 0, 0]} />
          {props.showLabels &&
            <Html name={"label"}
              center={true}
              position={[-66, position, Z]}>
              <p className={"led-label"}>{ledIndicator.label}</p>
            </Html>}
        </group>
      })}
  </group>
}

export const ElectronicsBoxModel = (props: BoxTopBaseProps) => {
  return (
    // <div className={"electronics-box-3d-model"}>
      // <Canvas>
        <group dispose={null}
          rotation={[0, 0, Math.PI / 2]}>
          {/* <PerspectiveCamera makeDefault name="camera" fov={30} near={0.1} far={1000}
            position={[-150, 0, 300]}
            rotation={[0, -Math.PI / 6, -Math.PI / 2]} /> */}
          <pointLight intensity={2} position={[0, 0, 200]} rotation={[0, 0, 0]}
            distance={0} decay={0} />
          <directionalLight intensity={0.1}
            position={[-100, 0, 100]} rotation={[0, 0, 0]} />
          <ambientLight intensity={0.5} />
          <ElectronicsBox {...props} showLabels={false} />
        </group>
      // </Canvas>
    // </div>
  )
  // ** from main repo
  // return <div className={"electronics-box-3d-model"}>
  //   <Canvas>
  //     <group dispose={null}
  //       rotation={[0, 0, Math.PI / 2]}>
  //       <PerspectiveCamera makeDefault name="camera" fov={30} near={0.1} far={1000}
  //         position={[-150, 0, 300]}
  //         rotation={[0, -Math.PI / 6, -Math.PI / 2]} />
  //       <pointLight intensity={2} position={[0, 0, 200]} rotation={[0, 0, 0]}
  //         distance={0} decay={0} />
  //       <directionalLight intensity={0.1}
  //         position={[-100, 0, 100]} rotation={[0, 0, 0]} />
  //       <ambientLight intensity={0.5} />
  //       <ElectronicsBox {...props} showLabels={true} />
  //     </group>
  //   </Canvas>
  // </div>
}

export default ElectronicsBoxModel