import { type CSSProperties, useId, useMemo, useState } from "react";
import { BODY_VIEWBOX, DEFAULT_COLOR_NAME, ORGAN_IDS } from "./constants";
import { createStrictColorPalette } from "./lib";
import { OrganSvg } from "./OrganSvg";
import { Body } from "./svg/Body";
import type { HpoVisualizerProps, OrganConfig, OrganId, StrictColorPalette } from "./types";
import { useOrganInteraction } from "./useOrganInteraction";
import { useZoom } from "./useZoom";
import { ZoomControls } from "./ZoomControls";

/**
 * Default positions and viewBox dimensions for each organ in the visualizer
 * Positions are relative to Body SVG coordinate system (122x325 viewBox)
 * viewBox: original coordinate system of the organ SVG
 */
const ORGAN_POSITIONS: Record<
  OrganId,
  { x: number; y: number; width: number; height: number; viewBox: string }
> = {
  growth: { x: 104, y: 1, width: 12, height: 349, viewBox: "0 0 12 349" },
  constitutional: { x: 0, y: 0, width: 122, height: 358, viewBox: "0 0 122 358" },
  thoracicCavity: { x: 31, y: 79, width: 60, height: 50, viewBox: "0 0 60 50" },
  lung: { x: 35, y: 85, width: 52, height: 41, viewBox: "0 0 52 41" },
  breast: { x: 26, y: 103, width: 70, height: 30, viewBox: "0 0 70 30" },
  heart: { x: 50, y: 91, width: 22, height: 23, viewBox: "0 0 22 23" },
  digestive: { x: 41, y: 119, width: 42, height: 86, viewBox: "0 0 42 86" },
  kidney: { x: 49, y: 162, width: 24, height: 30, viewBox: "0 0 24 30" },
  limbs: { x: 31, y: 146, width: 87, height: 203, viewBox: "0 0 87 203" },
  integument: { x: 6, y: 91, width: 90, height: 235, viewBox: "0 0 90 235" },
  head: { x: 35, y: 4, width: 52, height: 68, viewBox: "0 0 52 68" },
  eye: { x: 69, y: 24, width: 12, height: 12, viewBox: "0 0 12 12" },
  ear: { x: 34, y: 30, width: 13, height: 13, viewBox: "0 0 13 13" },
  nervous: { x: 27, y: 4, width: 68, height: 185, viewBox: "0 0 68 185" },
  voice: { x: 54, y: 56, width: 14, height: 22, viewBox: "0 0 14 22" },
  metabolism: { x: 50, y: 128, width: 22, height: 21, viewBox: "0 0 22 21" },
  cell: { x: 27, y: 185, width: 11, height: 18, viewBox: "0 0 11 18" },
  endocrine: { x: 50, y: 64, width: 26, height: 99, viewBox: "0 0 26 99" },
  neoplasm: { x: 81, y: 80, width: 11, height: 12, viewBox: "0 0 11 12" },
  immune: { x: 57, y: 81, width: 30, height: 58, viewBox: "0 0 30 58" },
  muscle: { x: 13, y: 79, width: 83, height: 182, viewBox: "0 0 83 182" },
  prenatal: { x: 44, y: 170, width: 34, height: 24, viewBox: "0 0 34 24" },
  blood: { x: 6, y: 182, width: 6, height: 10, viewBox: "0 0 6 10" },
};

const BACKGROUND_ORGANS: readonly OrganId[] = ["growth"] as const;
const FOREGROUND_ORGANS: readonly OrganId[] = [
  "constitutional",
  "thoracicCavity",
  "lung",
  "breast",
  "heart",
  "digestive",
  "kidney",
  "limbs",
  "integument",
  "head",
  "eye",
  "ear",
  "nervous",
  "voice",
  "metabolism",
  "cell",
  "endocrine",
  "neoplasm",
  "immune",
  "muscle",
  "prenatal",
  "blood",
] as const;

type RenderEntry = { type: "organ"; organId: OrganId } | { type: "body" };

const MIN_ZOOM = 1;

/**
 * HPO Visualizer - Interactive human organ visualization component
 *
 * Features:
 * - Displays organs as interactive SVG graphics
 * - Hover effect with visual highlighting
 * - Click to select/deselect organs
 * - Hover effects work independently even when an organ is selected
 * - Exposes hover and selection state via callbacks
 * - Zoom and pan support with mouse wheel and drag
 */
export function HpoVisualizer({
  organs,
  visibleOrgans,
  hoveredOrgan: controlledHovered,
  selectedOrgan: controlledSelected,
  onHover,
  onSelect,
  colorPalette: inputColorPalette,
  width = "100%",
  height = "100%",
  className,
  style,
  maxZoom = 5,
  wheelZoom = true,
}: HpoVisualizerProps) {
  const visualizerID = useId();
  const [isHovering, setIsHovering] = useState(false);

  // Zoom and pan functionality
  const {
    zoom,
    pan,
    zoomIn,
    zoomOut,
    resetZoom,
    handleMouseDown,
    handleMouseMove,
    handleMouseUp,
    isDragging,
    isDefaultZoom,
    containerRef,
  } = useZoom({ minZoom: MIN_ZOOM, maxZoom, wheelZoom, viewBox: BODY_VIEWBOX });

  // Create strict color palette with all required colors and alpha values
  const colorPalette: StrictColorPalette = useMemo(
    () => createStrictColorPalette(inputColorPalette),
    [inputColorPalette],
  );
  // Determine which organs are visible based on visibleOrgans prop
  // Priority: visibleOrgans > organs > all organs
  // - visibleOrgans (array): only the specified organs are visible
  // - visibleOrgans (empty array): no organs are visible
  // - visibleOrgans (undefined) + organs (array): organs from organs prop are visible
  // - both undefined: all organs are visible
  const visibleOrganIds = useMemo((): OrganId[] => {
    if (visibleOrgans !== undefined) {
      return visibleOrgans;
    }
    if (organs !== undefined) {
      return organs.map((o) => o.id);
    }
    return [...ORGAN_IDS];
  }, [visibleOrgans, organs]);

  // Create organ config map for quick lookup
  const organConfigMap = useMemo((): Map<OrganId, OrganConfig> => {
    const map = new Map<OrganId, OrganConfig>();
    if (organs) {
      for (const config of organs) {
        map.set(config.id, config);
      }
    }
    return map;
  }, [organs]);

  // Use the interaction hook
  const { handlers, isHovered, isSelected, state } = useOrganInteraction({
    hoveredOrgan: controlledHovered,
    selectedOrgan: controlledSelected,
    onHover,
    onSelect,
  });

  const selectedOrganId = state.selectedOrgan;
  const renderEntries = useMemo(() => {
    const base: RenderEntry[] = [
      ...BACKGROUND_ORGANS.map((organId): RenderEntry => ({ type: "organ", organId })),
      { type: "body" },
      ...FOREGROUND_ORGANS.map((organId): RenderEntry => ({ type: "organ", organId })),
    ];

    if (!selectedOrganId) {
      return base;
    }

    const selectedIndex = base.findIndex(
      (entry) => entry.type === "organ" && entry.organId === selectedOrganId,
    );
    if (selectedIndex === -1) {
      return base;
    }

    const selectedEntry = base[selectedIndex];
    if (!selectedEntry || selectedEntry.type !== "organ") {
      return base;
    }
    return [...base.slice(0, selectedIndex), ...base.slice(selectedIndex + 1), selectedEntry];
  }, [selectedOrganId]);

  const {
    padding,
    paddingTop,
    paddingRight,
    paddingBottom,
    paddingLeft,
    paddingInline,
    paddingInlineStart,
    paddingInlineEnd,
    paddingBlock,
    paddingBlockStart,
    paddingBlockEnd,
    ...containerStyleOverrides
  } = style ?? {};

  const containerStyle: CSSProperties = {
    display: "flex",
    justifyContent: "center",
    alignItems: "flex-end",
    userSelect: "none",
    position: "relative",
    width: width,
    height: height,
    ...containerStyleOverrides,
    // Apply overflow after style spread to ensure it takes precedence
    overflow: "clip",
  };

  const contentStyle: CSSProperties = {
    display: "flex",
    justifyContent: "center",
    alignItems: "flex-end",
    width: "100%",
    height: "100%",
    boxSizing: "border-box",
    padding,
    paddingTop,
    paddingRight,
    paddingBottom,
    paddingLeft,
    paddingInline,
    paddingInlineStart,
    paddingInlineEnd,
    paddingBlock,
    paddingBlockStart,
    paddingBlockEnd,
  };

  const svgStyle: CSSProperties = {
    width: "100%",
    height: "100%",
    display: "block",
    cursor: isDragging ? "grabbing" : zoom > 1 ? "grab" : "default",
    overflow: "visible",
  };

  const viewBox = `0 0 ${BODY_VIEWBOX.width} ${BODY_VIEWBOX.height}`;
  const centerX = BODY_VIEWBOX.width / 2;
  const centerY = BODY_VIEWBOX.height / 2;
  const zoomTransform = `translate(${centerX} ${centerY}) scale(${zoom}) translate(${-centerX} ${-centerY})`;

  // Helper function to render an organ
  const renderOrgan = (organId: OrganId, isVisible: boolean) => {
    const position = ORGAN_POSITIONS[organId];
    const config = organConfigMap.get(organId);

    // Check if organ should be visible based on config style (different from visibleOrgans)
    if (config?.style?.visible === false) {
      return null;
    }

    const x = position.x;
    const y = position.y;
    const width = position.width;
    const height = position.height;

    return (
      <OrganSvg
        key={`${visualizerID}-${organId}`}
        organId={organId}
        config={config}
        colorPalette={colorPalette}
        isHovered={isHovered(organId)}
        isSelected={isSelected(organId)}
        onMouseEnter={() => handlers.handleMouseEnter(organId)}
        onMouseLeave={handlers.handleMouseLeave}
        onClick={() => handlers.handleClick(organId)}
        x={x}
        y={y}
        width={width}
        height={height}
        viewBox={position.viewBox}
        showOutline={config?.showOutline}
        isVisible={isVisible}
      />
    );
  };

  return (
    <div
      className={className}
      style={containerStyle}
      onMouseEnter={() => setIsHovering(true)}
      onMouseLeave={() => {
        setIsHovering(false);
        handleMouseUp();
      }}
      onMouseDown={handleMouseDown}
      onMouseMove={handleMouseMove}
      onMouseUp={handleMouseUp}
      role="application"
      // biome-ignore lint/a11y/noNoninteractiveTabindex: Required for keyboard accessibility in zoom/pan application
      tabIndex={0}
    >
      <div style={contentStyle}>
        <svg
          ref={containerRef}
          width="100%"
          height="100%"
          viewBox={viewBox}
          preserveAspectRatio="xMidYMid meet"
          style={svgStyle}
          aria-label="Human organ visualizer"
        >
          <title>Human organ visualizer</title>
          <g transform={`translate(${pan.x} ${pan.y})`}>
            <g transform={zoomTransform}>
              {renderEntries.map((entry) => {
                if (entry.type === "body") {
                  return (
                    <Body
                      key={`${visualizerID}-body`}
                      colorScale={colorPalette[DEFAULT_COLOR_NAME]}
                      style={{
                        fill: "#fff",
                      }}
                    />
                  );
                }
                return renderOrgan(entry.organId, visibleOrganIds.includes(entry.organId));
              })}
            </g>
          </g>
        </svg>
      </div>
      <ZoomControls
        onZoomIn={zoomIn}
        onZoomOut={zoomOut}
        onReset={resetZoom}
        showResetButton={!isDefaultZoom}
        zoom={zoom}
        minZoom={MIN_ZOOM}
        maxZoom={maxZoom}
        isVisible={isHovering}
      />
    </div>
  );
}
