"use client";

import {
  ChangeEvent,
  ReactNode,
  forwardRef,
  useContext,
  useImperativeHandle,
  useRef,
  useState,
} from "react";

import {
  SceneViewContext,
  SceneViewContextType,
} from "../providers/scene-view-provider";
import { Mesh, MeshStandardMaterial } from "three";
import { CustomEvent } from "../three/SceneView";
import { RangeSlider } from "./RangeSlider";

function Toggle({
  title,
  onChange,
  defaultChecked,
  disabled = false,
}: {
  title: string;
  onChange: (e: ChangeEvent<HTMLInputElement>) => void;
  defaultChecked?: boolean;
  disabled?: boolean;
}) {
  return (
    <label className="group">
      <div className="inline-flex items-center cursor-pointer group-has-[input:disabled]:cursor-default">
        <input
          type="checkbox"
          value=""
          className="sr-only peer"
          onChange={onChange}
          defaultChecked={defaultChecked ? true : false}
          disabled={disabled}
        />
        <div className="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-gray-300 rounded-full peer dark:bg-gray-400 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-400 peer-checked:bg-blue-600 dark:peer-checked:bg-blue-400"></div>
        <span className="ms-3 text-xs xl:text-sm font-medium text-gray-500 dark:text-gray-400 group-has-[input:disabled]:text-gray-200">
          {title}
        </span>
      </div>
    </label>
  );
}

enum Position {
  Start,
  Center,
  End,
}

interface AccordionRef {
  open: (b: boolean) => void;
}

interface AccordionProps {
  title: string;
  position: Position;
  open: boolean;
  children: ReactNode;
}

const Accordion = forwardRef<AccordionRef, AccordionProps>(
  ({ children, title, position, open }, ref) => {
    const [expanded, setExpanded] = useState<boolean>(open);
    const accordionBodyRef = useRef<HTMLDivElement>(null);

    useImperativeHandle(ref, () => ({
      open: (b: boolean) => setExpanded(b),
    }));

    function handleClick() {
      if (!accordionBodyRef.current) return;

      setExpanded(!expanded);
    }

    const className =
      position === Position.Center
        ? "flex items-center justify-between w-full p-5 font-medium rtl:text-right text-gray-500 border border-b border-gray-200 dark:border-gray-400 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 gap-3 hover:cursor-pointer"
        : "flex items-center justify-between w-full p-5 font-medium rtl:text-right text-gray-500 border border-b border-gray-200 rounded-t dark:border-gray-400 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 gap-3 hover:cursor-pointer";

    return (
      <div>
        <h2 id="accordion-collapse-heading">
          <button
            type="button"
            className={className}
            aria-expanded={expanded ? "true" : "false"}
            onClick={handleClick}
          >
            <span>{title}</span>
            <svg
              data-accordion-icon
              className={
                expanded ? "w-3 h-3 shrink-0" : "w-3 h-3 rotate-180 shrink-0"
              }
              aria-hidden="true"
              xmlns="http://www.w3.org/2000/svg"
              fill="none"
              viewBox="0 0 10 6"
            >
              <path
                stroke="currentColor"
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth="2"
                d="M9 5 5 1 1 5"
              />
            </svg>
          </button>
        </h2>
        <div
          id="accordion-collapse-body"
          ref={accordionBodyRef}
          aria-labelledby="accordion-collapse-heading"
          className={expanded ? "" : "hidden"}
        >
          <div className="p-5 border border-gray-200 dark:border-gray-400 dark:bg-gray-700">
            {children}
          </div>
        </div>
      </div>
    );
  }
);
Accordion.displayName = "Accordion";

export function Form() {
  const svgContainerRef = useRef<HTMLDivElement>(null);
  const accordionRef1 = useRef<AccordionRef>(null);
  const accordionRef0 = useRef<AccordionRef>(null);

  const [emptyProfile, setEmptyProfile] = useState<boolean>(false);
  const { sceneView } = useContext(SceneViewContext) as SceneViewContextType;

  function handleChangeSlicingBox() {
    if (!sceneView) return;

    sceneView.toggleClippingBox();
  }

  function handleChangeCG() {
    if (!sceneView) return;

    sceneView.toggleCoordinateGrid();
  }

  function handleChangeWireframe() {
    if (!sceneView) return;

    sceneView.toggleWireFrame();
  }

  function handleCheckboxChange(name: string) {
    if (!sceneView) return;

    sceneView.toggleLayerVisibility(name);
  }

  function handleChangeTopography() {
    if (!sceneView) return;

    sceneView.toggleTopography();
  }

  function handleDrilling(e: ChangeEvent) {
    if (!sceneView) return;

    if ((e.target as HTMLInputElement).checked) {
      // Enable raycaster with callback to handle svg element
      sceneView.enableRaycaster(handleSVGCreated);
    } else {
      sceneView.disableRaycaster();
    }
  }

  function handleSVGCreated(e: CustomEvent) {
    if (
      !svgContainerRef.current ||
      !accordionRef0.current ||
      !accordionRef1.current
    )
      return;

    while (svgContainerRef.current.children.length > 0) {
      const c = svgContainerRef.current.children[0];
      svgContainerRef.current.removeChild(c);
    }

    if (e.detail && e.detail.element) {
      setEmptyProfile(false);
      svgContainerRef.current.appendChild(e.detail.element);
      accordionRef0.current.open(false);
      accordionRef1.current.open(true);
    } else {
      setEmptyProfile(true);
      accordionRef0.current.open(false);
      accordionRef1.current.open(true);
    }
  }

  function handleExport() {
    if (!sceneView) return;

    sceneView.exportOBJ();
  }

  function handleExplode(e: ChangeEvent<HTMLInputElement>) {
    if (!sceneView) return;

    if (e.target.checked) {
      sceneView.explode(true);
      // setExploded(true);
    } else {
      sceneView.explode(false);
      // setExploded(false);
    }
  }

  return (
    <div className="w-full max-h-full min-h-0 flex flex-col gap-2 dark:bg-gray-700">
      <div className="w-full flex justify-end">
        <button
          onClick={handleExport}
          className="text-white bg-red-400 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-red-600 dark:hover:bg-red-700 focus:outline-none dark:focus:ring-red-800 hover:cursor-pointer"
        >
          Export as .obj
        </button>
      </div>
      <div className="border border-gray-200 dark:border-gray-400 rounded grid grid-cols-2 gap-y-2 p-2">
        <Toggle title="Slicing Box" onChange={handleChangeSlicingBox} />
        <Toggle title="Virtual Profile" onChange={handleDrilling} />
        <Toggle title="Coordinate Grid" onChange={handleChangeCG} />
        <Toggle title="Wireframe" onChange={handleChangeWireframe} />
        <Toggle title="Topography (OSM)" onChange={handleChangeTopography} />
        <Toggle title="Explode" onChange={handleExplode} />
      </div>
      <div className="px-2 pt-2 border border-gray-200 dark:border-gray-400 rounded">
        <RangeSlider></RangeSlider>
      </div>

      <div className="overflow-y-auto">
        <Accordion
          title="Layers"
          position={Position.Start}
          open={true}
          ref={accordionRef0}
        >
          {
            <div className="flex flex-col gap-2">
              {sceneView?.model.children.map((child) => {
                const key = `toggle-visibility-${child.name}`;
                const color = `#${(
                  (child as Mesh).material as MeshStandardMaterial
                ).color.getHexString()}`;
                const visible = (child as Mesh).visible;

                return (
                  <div
                    key={key}
                    className="flex items-center justify-start gap-2.5 border-b border-gray-200 dark:border-gray-400 py-1 dark:text-gray-400"
                  >
                    <span
                      className="inline-block w-5 h-5 flex-none rounded"
                      style={{
                        backgroundColor: color,
                      }}
                    ></span>
                    <input
                      id={key}
                      type="checkbox"
                      onChange={() => handleCheckboxChange(child.name)}
                      className="hover:cursor-pointer"
                      defaultChecked={visible ? true : false}
                    />
                    <label
                      htmlFor={key}
                      className="font-light text-gray-500 dark:text-gray-400"
                    >
                      {child.name}
                    </label>
                  </div>
                );
              })}
            </div>
          }
        </Accordion>
        <Accordion
          title="Virtual Profile"
          position={Position.Center}
          open={false}
          ref={accordionRef1}
        >
          {emptyProfile ? (
            <div className="font-light text-gray-500 dark:text-gray-400 text-sm">
              Virtual profile does not intersect the model.
            </div>
          ) : null}
          <div ref={svgContainerRef} className="dark:bg-gray-400">
            <div className="font-light text-gray-500 dark:text-gray-400 dark:bg-gray-700 text-sm">
              Please enable the Virtual Profile toggle and select a profile
              position!
            </div>
          </div>
        </Accordion>
      </div>
    </div>
  );
}
