import React, { useState, useEffect, useCallback, useRef } from 'react';
import type { CADLayer, CADProperties, CADElement } from '../../types/cad.types';
import './PropertiesPanel.css';

// ─── Types (local-only, not duplicated from cad.types.ts) ─────────────────────

interface YjsDocumentLike {
  layers: {
    forEach: (cb: (layer: CADLayer, id: string) => void) => void;
    observe: (cb: () => void) => void;
    unobserve: (cb: () => void) => void;
  };
  elements: {
    toArray: () => CADElement[];
    observe: (cb: () => void) => void;
    unobserve: (cb: () => void) => void;
  };
  updateElement: (id: string, updates: Partial<CADElement>) => void;
  getElementById: (id: string) => CADElement | undefined;
}

interface FabricObjectLike {
  id?: string;
  type?: string;
  left?: number;
  top?: number;
  width?: number;
  height?: number;
  angle?: number;
  fill?: string | null;
  stroke?: string | null;
  strokeWidth?: number;
  strokeDashArray?: number[];
  set: (props: Record<string, unknown>) => void;
  setCoords: () => void;
}

interface FabricCanvasLike {
  getActiveObject: () => FabricObjectLike | null;
  on: (event: string, handler: (...args: unknown[]) => void) => void;
  off: (event: string, handler: (...args: unknown[]) => void) => void;
  renderAll: () => void;
  fire: (event: string, payload?: unknown) => void;
}

interface CanvasRefLike {
  getCanvas?: () => FabricCanvasLike;
  getSelectedElements?: () => FabricObjectLike[];
}

interface PropertiesPanelProps {
  canvasRef: React.RefObject<CanvasRefLike | null>;
  yjsDocRef: React.RefObject<YjsDocumentLike | null>;
}

// ─── Helpers ─────────────────────────────────────────────────────────────────

const LINE_TYPE_DASH_MAP: Record<string, number[] | null> = {
  solid: null,
  dashed: [8, 4],
  dotted: [2, 3],
};

function dashArrayToLineType(dashArray?: number[] | null): 'solid' | 'dashed' | 'dotted' {
  if (!dashArray || dashArray.length === 0) return 'solid';
  if (dashArray[0] <= 2) return 'dotted';
  return 'dashed';
}

function clampNumber(value: string, min: number, max: number): number {
  const n = parseFloat(value);
  if (Number.isNaN(n)) return min;
  return Math.min(Math.max(n, min), max);
}

// ─── Component ───────────────────────────────────────────────────────────────

const PropertiesPanel: React.FC<PropertiesPanelProps> = ({ canvasRef, yjsDocRef }) => {
  const [selectedElement, setSelectedElement] = useState<CADElement | null>(null);
  const [layers, setLayers] = useState<CADLayer[]>([]);
  const [fabricObject, setFabricObject] = useState<FabricObjectLike | null>(null);
  const fabricCanvasRef = useRef<FabricCanvasLike | null>(null);

  // Debounce timer for Yjs updates (300ms) — no new dependency needed
  const yjsDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  // Clear debounce timer on unmount
  useEffect(() => {
    return () => {
      if (yjsDebounceRef.current) {
        clearTimeout(yjsDebounceRef.current);
      }
    };
  }, []);

  // ── Sync layers from YjsDocument ──────────────────────────────────────────
  useEffect(() => {
    const yjsDoc = yjsDocRef?.current;
    if (!yjsDoc) return;

    const syncLayers = () => {
      const arr: CADLayer[] = [];
      yjsDoc.layers.forEach((layer, id) => {
        arr.push({ ...layer, id });
      });
      arr.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
      setLayers(arr);
    };

    syncLayers();
    yjsDoc.layers.observe(syncLayers);
    return () => yjsDoc.layers.unobserve(syncLayers);
  }, [yjsDocRef]);

  // ── Sync elements from YjsDocument (for external updates) ─────────────────
  useEffect(() => {
    const yjsDoc = yjsDocRef?.current;
    if (!yjsDoc) return;

    const syncElements = () => {
      if (selectedElement) {
        const updated = yjsDoc.getElementById(selectedElement.id);
        if (updated) {
          setSelectedElement(updated);
        }
      }
    };

    yjsDoc.elements.observe(syncElements);
    return () => yjsDoc.elements.unobserve(syncElements);
  }, [yjsDocRef, selectedElement]);

  // ── Wire fabric selection events ──────────────────────────────────────────
  useEffect(() => {
    const ref = canvasRef?.current;
    if (!ref || !ref.getCanvas) return;

    const fabricCanvas = ref.getCanvas();
    if (!fabricCanvas) return;

    fabricCanvasRef.current = fabricCanvas;

    const handleSelection = () => {
      const active = fabricCanvas.getActiveObject();
      if (!active) {
        setSelectedElement(null);
        setFabricObject(null);
        return;
      }

      setFabricObject(active);

      // Try to match with YjsDocument element by id
      const yjsDoc = yjsDocRef?.current;
      const elementId = (active as FabricObjectLike).id;
      if (elementId && yjsDoc) {
        const el = yjsDoc.getElementById(elementId);
        if (el) {
          setSelectedElement(el);
          return;
        }
      }

      // Fallback: build a transient element from fabric properties
      setSelectedElement({
        id: (active as FabricObjectLike).id ?? 'transient',
        type: (active as FabricObjectLike).type ?? 'unknown',
        layerId: 'default',
        x: active.left ?? 0,
        y: active.top ?? 0,
        width: active.width ?? 0,
        height: active.height ?? 0,
        properties: {
          fill: (active.fill as string) ?? 'transparent',
          stroke: active.stroke ?? '#000000',
          strokeWidth: active.strokeWidth ?? 1,
          rotation: active.angle ?? 0,
          lineType: dashArrayToLineType(active.strokeDashArray),
        },
      });
    };

    const handleCleared = () => {
      setSelectedElement(null);
      setFabricObject(null);
    };

    fabricCanvas.on('selection:created', handleSelection);
    fabricCanvas.on('selection:updated', handleSelection);
    fabricCanvas.on('selection:cleared', handleCleared);

    return () => {
      fabricCanvas.off('selection:created', handleSelection);
      fabricCanvas.off('selection:updated', handleSelection);
      fabricCanvas.off('selection:cleared', handleCleared);
    };
  }, [canvasRef, yjsDocRef]);

  // ── Update handlers (Panel → Canvas + YjsDocument) ──────────────────────────

  const updateFabricAndYjs = useCallback(
    (updates: Partial<CADElement> & { properties?: Partial<CADProperties> }) => {
      if (!selectedElement) return;

      // Debounce Yjs write (300ms) — only write after user pauses typing
      if (yjsDebounceRef.current) {
        clearTimeout(yjsDebounceRef.current);
      }
      const elId = selectedElement.id;
      yjsDebounceRef.current = setTimeout(() => {
        const yjsDoc = yjsDocRef?.current;
        if (yjsDoc) {
          yjsDoc.updateElement(elId, updates);
        }
        yjsDebounceRef.current = null;
      }, 300);

      // Update fabric object directly (immediate, no debounce)
      const fabricObj = fabricObject;
      const fabricCanvas = fabricCanvasRef.current;
      if (fabricObj && fabricCanvas) {
        if (updates.x !== undefined) fabricObj.set({ left: updates.x });
        if (updates.y !== undefined) fabricObj.set({ top: updates.y });
        if (updates.width !== undefined) fabricObj.set({ width: updates.width });
        if (updates.height !== undefined) fabricObj.set({ height: updates.height });
        if (updates.properties?.rotation !== undefined)
          fabricObj.set({ angle: updates.properties.rotation });
        if (updates.properties?.fill !== undefined)
          fabricObj.set({ fill: updates.properties.fill });
        if (updates.properties?.stroke !== undefined)
          fabricObj.set({ stroke: updates.properties.stroke });
        if (updates.properties?.strokeWidth !== undefined)
          fabricObj.set({ strokeWidth: updates.properties.strokeWidth });
        if (updates.properties?.lineType !== undefined) {
          const dash = LINE_TYPE_DASH_MAP[updates.properties.lineType];
          fabricObj.set({ strokeDashArray: dash });
        }
        fabricObj.setCoords();
        fabricCanvas.renderAll();
      }

      // Update local state (immediate, no debounce)
      setSelectedElement((prev) => {
        if (!prev) return prev;
        return {
          ...prev,
          ...updates,
          properties: { ...prev.properties, ...updates.properties },
        };
      });
    },
    [selectedElement, fabricObject, yjsDocRef]
  );

  // ── Field change handlers ──────────────────────────────────────────────────

  const handleNumberChange = (field: 'x' | 'y' | 'width' | 'height', value: string) => {
    const n = clampNumber(value, -100000, 100000);
    updateFabricAndYjs({ [field]: n });
  };

  const handleRotationChange = (value: string) => {
    const n = clampNumber(value, -360, 360);
    updateFabricAndYjs({ properties: { rotation: n } });
  };

  const handleFillChange = (value: string) => {
    updateFabricAndYjs({ properties: { fill: value } });
  };

  const handleStrokeChange = (value: string) => {
    updateFabricAndYjs({ properties: { stroke: value } });
  };

  const handleStrokeWidthChange = (value: string) => {
    const n = clampNumber(value, 0, 100);
    updateFabricAndYjs({ properties: { strokeWidth: n } });
  };

  const handleLineTypeChange = (value: 'solid' | 'dashed' | 'dotted') => {
    updateFabricAndYjs({ properties: { lineType: value } });
  };

  const handleLayerChange = (value: string) => {
    updateFabricAndYjs({ layerId: value });
  };

  // ── Render ──────────────────────────────────────────────────────────────────

  if (!selectedElement) {
    return (
      <div className="properties-panel-empty">
        <div className="properties-panel-empty-icon" aria-hidden="true">⚙️</div>
        <p className="properties-panel-empty-text">
          Kein Element ausgewählt. Wähle ein Element auf dem Canvas, um seine Eigenschaften zu bearbeiten.
        </p>
      </div>
    );
  }

  const props = selectedElement.properties;
  const fillValue = props.fill ?? 'transparent';
  const strokeValue = props.stroke ?? '#000000';
  const strokeWidthValue = props.strokeWidth ?? 1;
  const rotationValue = props.rotation ?? 0;
  const lineTypeValue = props.lineType ?? 'solid';

  return (
    <div className="properties-panel" role="form" aria-label="Element Properties">
      {/* Element info header */}
      <div className="properties-panel-header">
        <span className="properties-panel-type-badge">{selectedElement.type}</span>
        <span className="properties-panel-id">ID: {selectedElement.id.substring(0, 12)}</span>
      </div>

      {/* Position section */}
      <fieldset className="properties-panel-fieldset">
        <legend>Position</legend>
        <div className="properties-panel-row">
          <label className="properties-panel-label" htmlFor="prop-x">
            X
            <input
              id="prop-x"
              type="number"
              className="properties-panel-input"
              value={Math.round(selectedElement.x * 100) / 100}
              onChange={(e) => handleNumberChange('x', e.target.value)}
              step="1"
            />
          </label>
          <label className="properties-panel-label" htmlFor="prop-y">
            Y
            <input
              id="prop-y"
              type="number"
              className="properties-panel-input"
              value={Math.round(selectedElement.y * 100) / 100}
              onChange={(e) => handleNumberChange('y', e.target.value)}
              step="1"
            />
          </label>
        </div>
      </fieldset>

      {/* Size section */}
      <fieldset className="properties-panel-fieldset">
        <legend>Größe</legend>
        <div className="properties-panel-row">
          <label className="properties-panel-label" htmlFor="prop-width">
            Breite
            <input
              id="prop-width"
              type="number"
              className="properties-panel-input"
              value={Math.round(selectedElement.width * 100) / 100}
              onChange={(e) => handleNumberChange('width', e.target.value)}
              step="1"
              min="0"
            />
          </label>
          <label className="properties-panel-label" htmlFor="prop-height">
            Höhe
            <input
              id="prop-height"
              type="number"
              className="properties-panel-input"
              value={Math.round(selectedElement.height * 100) / 100}
              onChange={(e) => handleNumberChange('height', e.target.value)}
              step="1"
              min="0"
            />
          </label>
        </div>
      </fieldset>

      {/* Rotation section */}
      <fieldset className="properties-panel-fieldset">
        <legend>Rotation</legend>
        <label className="properties-panel-label" htmlFor="prop-rotation">
          Winkel (Grad)
          <input
            id="prop-rotation"
            type="number"
            className="properties-panel-input"
            value={Math.round(rotationValue * 100) / 100}
            onChange={(e) => handleRotationChange(e.target.value)}
            step="1"
            min="-360"
            max="360"
          />
        </label>
      </fieldset>

      {/* Appearance section */}
      <fieldset className="properties-panel-fieldset">
        <legend>Erscheinungsbild</legend>
        <label className="properties-panel-label" htmlFor="prop-fill">
          Füllfarbe
          <input
            id="prop-fill"
            type="color"
            className="properties-panel-color-input"
            value={fillValue === 'transparent' ? '#ffffff' : fillValue}
            onChange={(e) => handleFillChange(e.target.value)}
          />
          <input
            type="text"
            className="properties-panel-text-input"
            value={fillValue}
            onChange={(e) => handleFillChange(e.target.value)}
            placeholder="#RRGGBB oder transparent"
          />
        </label>
        <label className="properties-panel-label" htmlFor="prop-stroke">
          Linienfarbe
          <input
            id="prop-stroke"
            type="color"
            className="properties-panel-color-input"
            value={strokeValue}
            onChange={(e) => handleStrokeChange(e.target.value)}
          />
          <input
            type="text"
            className="properties-panel-text-input"
            value={strokeValue}
            onChange={(e) => handleStrokeChange(e.target.value)}
            placeholder="#RRGGBB"
          />
        </label>
        <label className="properties-panel-label" htmlFor="prop-stroke-width">
          Linienstärke
          <input
            id="prop-stroke-width"
            type="number"
            className="properties-panel-input"
            value={strokeWidthValue}
            onChange={(e) => handleStrokeWidthChange(e.target.value)}
            step="0.5"
            min="0"
          />
        </label>
        <label className="properties-panel-label" htmlFor="prop-line-type">
          Linientyp
          <select
            id="prop-line-type"
            className="properties-panel-select"
            value={lineTypeValue}
            onChange={(e) => handleLineTypeChange(e.target.value as 'solid' | 'dashed' | 'dotted')}
          >
            <option value="solid">Solid</option>
            <option value="dashed">Dashed</option>
            <option value="dotted">Dotted</option>
          </select>
        </label>
      </fieldset>

      {/* Layer assignment section */}
      <fieldset className="properties-panel-fieldset">
        <legend>Layer</legend>
        <label className="properties-panel-label" htmlFor="prop-layer">
          Zugewiesener Layer
          <select
            id="prop-layer"
            className="properties-panel-select"
            value={selectedElement.layerId}
            onChange={(e) => handleLayerChange(e.target.value)}
          >
            {layers.length === 0 ? (
              <option value="default">Default</option>
            ) : (
              layers.map((layer) => (
                <option key={layer.id} value={layer.id}>
                  {layer.name}{layer.locked ? ' (gesperrt)' : ''}
                </option>
              ))
            )}
          </select>
        </label>
      </fieldset>
    </div>
  );
};

export default PropertiesPanel;
