import { useEffect, useRef, useState } from 'react';
import { autoLayoutForArtifacts, defaultLayout } from './artifact-layout';
import { isHtmlArtifact } from './artifact-utils';

export function useArtifactLayout({ state, refresh, spaceArtifactIds, activeSpaceId, moveArtifactToSpace }) {
  const [selectedArtifactId, setSelectedArtifactId] = useState(null);
  const [minimizedIds, setMinimizedIds] = useState({});
  const [hoveredDockId, setHoveredDockId] = useState(null);
  const [fullscreenId, setFullscreenId] = useState(null);
  const [confirmDeleteId, setConfirmDeleteId] = useState(null);
  const [cardSizes, setCardSizes] = useState({});
  const [cardPositions, setCardPositions] = useState({});
  const [cardZ, setCardZ] = useState({});
  const [draggingId, setDraggingId] = useState(null);
  const [resizingId, setResizingId] = useState(null);
  const [pointerDropSpaceId, setPointerDropSpaceId] = useState(null);
  const [layoutMode, setLayoutMode] = useState('auto');
  const [boardEl, setBoardEl] = useState(null);
  const [boardSize, setBoardSize] = useState({ width: 0, height: 0 });
  const nextZRef = useRef(20);
  const layoutHydratedRef = useRef(false);

  function toggleMinimize(id) {
    setMinimizedIds(m => ({ ...m, [id]: !m[id] }));
  }

  function getLayout(id, stableIndex) {
    const p = cardPositions[id];
    const s = cardSizes[id];
    const inSpace = spaceArtifactIds ? new Set(spaceArtifactIds) : null;
    const visibleCount = (state?.artifacts || []).filter(a =>
      a.content && !minimizedIds[a.id] && (!inSpace || inSpace.has(a.id))
    ).length;
    const d = defaultLayout(Math.max(0, stableIndex), Math.max(1, visibleCount), boardSize);
    return {
      x: p?.x ?? d.x,
      y: p?.y ?? d.y,
      width: s?.width ?? d.width,
      height: s?.height ?? d.height,
    };
  }

  function bringToFront(id) {
    nextZRef.current += 1;
    const z = nextZRef.current;
    setSelectedArtifactId(id);
    setCardZ(prev => (prev[id] === z ? prev : { ...prev, [id]: z }));
  }

  function resetArtifactLayout() {
    // Re-flow only the currently visible artifacts. Do NOT unminimize docked items —
    // the toolbar "reorganize" button should rearrange the open windows, not restore minimized ones.
    nextZRef.current = 20;
    setLayoutMode('auto');
    setCardZ({});
    // The auto-layout effect below will overwrite positions/sizes for visible artifacts on its next pass.
  }

  async function deleteArtifactById(id) {
    const res = await fetch(`/api/artifacts/${encodeURIComponent(id)}`, { method: 'DELETE' });
    // 404 means the row is already gone (e.g. a background dedupe/regeneration
    // collapse removed it after this card rendered). That is the desired end
    // state, so treat delete as idempotent: clear local layout + refresh rather
    // than surfacing a runtime error for a card the user wanted gone anyway.
    if (!res.ok && res.status !== 404) throw new Error(await res.text());
    setMinimizedIds(m => { const n = { ...m }; delete n[id]; return n; });
    setCardSizes(s => { const n = { ...s }; delete n[id]; return n; });
    setCardPositions(p => { const n = { ...p }; delete n[id]; return n; });
    setCardZ(z => { const n = { ...z }; delete n[id]; return n; });
    setConfirmDeleteId(null);
    await refresh();
  }

  function downloadArtifact(a) {
    const isHtml = isHtmlArtifact(a.kind);
    const body = isHtml ? (a.content || '') : (a.source_md || a.content || '');
    const mime = isHtml ? 'text/html' : 'text/markdown';
    const ext = isHtml ? '.html' : '.md';
    const blob = new Blob([body], { type: mime });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = (a.title || 'artifact').toLowerCase().replace(/\s+/g, '-') + ext;
    link.click();
    URL.revokeObjectURL(url);
  }

  function startDrag(e, id) {
    if (e.target.closest('button, .winbar i, .winbar-dl, .artifact-kind-badge, .confirm-label')) return;
    e.preventDefault();
    const wrap = e.currentTarget.closest('.artifact-window-wrap');
    const board = wrap?.closest('.canvas-board');
    if (!wrap) return;
    e.currentTarget.setPointerCapture?.(e.pointerId);
    setLayoutMode('manual');
    setDraggingId(id);
    bringToFront(id);
    const boardRect = board?.getBoundingClientRect();
    const wrapRect = wrap.getBoundingClientRect();
    const startLeft = boardRect ? wrapRect.left - boardRect.left + board.scrollLeft : 0;
    const startTop = boardRect ? wrapRect.top - boardRect.top + board.scrollTop : 0;
    const grabX = boardRect ? e.clientX - boardRect.left + board.scrollLeft - startLeft : 0;
    const grabY = boardRect ? e.clientY - boardRect.top + board.scrollTop - startTop : 0;
    const target = e.currentTarget;
    document.body.style.userSelect = 'none';
    let dropChipSpaceId = null;
    let dropChipPreview = false;
    function detectChipUnderPointer(clientX, clientY) {
      const el = document.elementFromPoint(clientX, clientY);
      if (!el) return null;
      const chip = el.closest('.space-chip');
      if (!chip) return null;
      const sid = chip.getAttribute('data-space-id');
      if (!sid) return null;
      // Only show as a drop target if dropping there would actually move the artifact.
      if (activeSpaceId && sid === activeSpaceId) return null;
      return sid;
    }
    function onMove(mv) {
      mv.preventDefault();
      const rect = board?.getBoundingClientRect();
      const newX = rect
        ? Math.max(0, mv.clientX - rect.left + board.scrollLeft - grabX)
        : startLeft;
      const newY = rect
        ? Math.max(0, mv.clientY - rect.top + board.scrollTop - grabY)
        : startTop;
      setCardPositions(p => ({ ...p, [id]: { x: newX, y: newY } }));

      const overSpaceId = detectChipUnderPointer(mv.clientX, mv.clientY);
      if (overSpaceId !== dropChipSpaceId) {
        dropChipSpaceId = overSpaceId;
        setPointerDropSpaceId(overSpaceId);
      }
      // Track whether the user actually hovered a chip so we can decide on drop.
      if (overSpaceId) dropChipPreview = true;
    }
    function onUp(up) {
      target.removeEventListener('pointermove', onMove);
      target.removeEventListener('pointerup', onUp);
      target.removeEventListener('pointercancel', onUp);
      target.releasePointerCapture?.(up.pointerId);
      document.body.style.userSelect = '';
      setDraggingId(null);
      setPointerDropSpaceId(null);
      // If released over a chip belonging to a different space, move the artifact and
      // revert its position so it doesn't leave a stale layout in this space.
      if (dropChipPreview && dropChipSpaceId && typeof moveArtifactToSpace === 'function') {
        moveArtifactToSpace(id, dropChipSpaceId);
        setCardPositions(p => {
          if (!(id in p)) return p;
          const next = { ...p };
          next[id] = { x: startLeft, y: startTop };
          return next;
        });
      }
    }
    target.addEventListener('pointermove', onMove);
    target.addEventListener('pointerup', onUp);
    target.addEventListener('pointercancel', onUp);
  }

  function startResize(e, id, dir) {
    e.preventDefault();
    e.stopPropagation();
    const wrap = e.currentTarget.closest('.artifact-window-wrap');
    const board = wrap.closest('.canvas-board');
    e.currentTarget.setPointerCapture?.(e.pointerId);
    setLayoutMode('manual');
    bringToFront(id);
    const startX = e.clientX;
    const startY = e.clientY;
    const boardRect = board?.getBoundingClientRect();
    const wrapRect = wrap.getBoundingClientRect();
    const startW = wrapRect.width;
    const startH = wrapRect.height;
    const startLeft = boardRect ? wrapRect.left - boardRect.left + board.scrollLeft : 0;
    const startTop = boardRect ? wrapRect.top - boardRect.top + board.scrollTop : 0;
    const boardW = board ? board.clientWidth  : Infinity;
    const boardH = board ? board.clientHeight : Infinity;
    const MIN_W = 280, MIN_H = 180, PAD = 8;
    const cursor = {
      n:'ns-resize', s:'ns-resize', e:'ew-resize', w:'ew-resize',
      ne:'nesw-resize', sw:'nesw-resize', nw:'nwse-resize', se:'nwse-resize',
    }[dir] || 'nwse-resize';
    const target = e.currentTarget;
    setResizingId(id);
    document.body.style.userSelect = 'none';
    document.body.style.cursor = cursor;
    function onMove(mv) {
      mv.preventDefault();
      let dx = mv.clientX - startX;
      let dy = mv.clientY - startY;
      let nx = startLeft, ny = startTop, nw = startW, nh = startH;
      if (dir.includes('w')) {
        if (startW - dx < MIN_W) dx = startW - MIN_W;
        if (startLeft + dx < PAD) dx = PAD - startLeft;
        nx = startLeft + dx;
        nw = startW - dx;
      }
      if (dir.includes('e')) {
        if (startW + dx < MIN_W) dx = MIN_W - startW;
        if (startLeft + startW + dx > boardW - PAD) dx = boardW - PAD - startLeft - startW;
        nw = startW + dx;
      }
      if (dir.includes('n')) {
        if (startH - dy < MIN_H) dy = startH - MIN_H;
        if (startTop + dy < PAD) dy = PAD - startTop;
        ny = startTop + dy;
        nh = startH - dy;
      }
      if (dir.includes('s')) {
        if (startH + dy < MIN_H) dy = MIN_H - startH;
        if (startTop + startH + dy > boardH - PAD) dy = boardH - PAD - startTop - startH;
        nh = startH + dy;
      }
      setCardSizes(s => ({ ...s, [id]: { width: nw, height: nh } }));
      if (dir.includes('w') || dir.includes('n')) {
        setCardPositions(p => ({ ...p, [id]: { x: nx, y: ny } }));
      }
    }
    function onUp(up) {
      target.removeEventListener('pointermove', onMove);
      target.removeEventListener('pointerup', onUp);
      target.removeEventListener('pointercancel', onUp);
      target.releasePointerCapture?.(up.pointerId);
      document.body.style.userSelect = '';
      document.body.style.cursor = '';
      setResizingId(null);
    }
    target.addEventListener('pointermove', onMove);
    target.addEventListener('pointerup', onUp);
    target.addEventListener('pointercancel', onUp);
  }

  useEffect(() => {
    if (!boardEl) return;
    const update = () => setBoardSize({
      width: boardEl.clientWidth,
      height: boardEl.clientHeight,
    });
    update();
    const ro = new ResizeObserver(update);
    ro.observe(boardEl);
    return () => ro.disconnect();
  }, [boardEl]);

  // Clamp persisted positions/sizes back into the visible board whenever the
  // board is (re)measured. Catches: positions saved from a larger window in a
  // prior session, cascade overshoot, and shrinking the window below saved
  // bounds — all of which previously left artifacts off-screen on reload.
  useEffect(() => {
    if (!layoutHydratedRef.current) return;
    if (!boardSize.width || !boardSize.height) return;
    const PAD = 8;
    const MIN_W = 280;
    const MIN_H = 180;
    setCardSizes(prev => {
      let changed = false;
      const next = { ...prev };
      const maxW = Math.max(MIN_W, boardSize.width - PAD * 2);
      const maxH = Math.max(MIN_H, boardSize.height - PAD * 2);
      for (const id in prev) {
        const s = prev[id];
        const w = Math.max(MIN_W, Math.min(maxW, s.width));
        const h = Math.max(MIN_H, Math.min(maxH, s.height));
        if (w !== s.width || h !== s.height) {
          next[id] = { width: w, height: h };
          changed = true;
        }
      }
      return changed ? next : prev;
    });
    setCardPositions(prev => {
      let changed = false;
      const next = { ...prev };
      for (const id in prev) {
        const p = prev[id];
        const cardW = cardSizes[id]?.width || 520;
        const cardH = cardSizes[id]?.height || 380;
        const maxX = Math.max(PAD, boardSize.width - cardW - PAD);
        const maxY = Math.max(PAD, boardSize.height - cardH - PAD);
        const x = Math.max(PAD, Math.min(maxX, p.x));
        const y = Math.max(PAD, Math.min(maxY, p.y));
        if (x !== p.x || y !== p.y) {
          next[id] = { x, y };
          changed = true;
        }
      }
      return changed ? next : prev;
    });
  }, [boardSize]);

  useEffect(() => {
    document.body.classList.toggle('is-dragging', Boolean(draggingId));
    document.body.classList.toggle('is-resizing', Boolean(resizingId));
    return () => {
      document.body.classList.remove('is-dragging');
      document.body.classList.remove('is-resizing');
    };
  }, [draggingId, resizingId]);

  useEffect(() => {
    try {
      const raw = localStorage.getItem('madarx:artifact-layout-v2');
      if (raw) {
        const data = JSON.parse(raw);
        if (data.positions) setCardPositions(data.positions);
        if (data.sizes)     setCardSizes(data.sizes);
        if (data.z)         setCardZ(data.z);
        if (data.minimized) setMinimizedIds(data.minimized);
        if (data.layoutMode === 'manual' || data.layoutMode === 'auto') setLayoutMode(data.layoutMode);
        if (typeof data.nextZ === 'number') nextZRef.current = data.nextZ;
      }
    } catch {}
    layoutHydratedRef.current = true;
  }, []);

  useEffect(() => {
    if (!state) return;
    const inSpace = spaceArtifactIds ? new Set(spaceArtifactIds) : null;
    const arts = (state.artifacts || []).filter(a =>
      a.content && !minimizedIds[a.id] && (!inSpace || inSpace.has(a.id))
    );
    if (layoutMode === 'auto') {
      const layouts = autoLayoutForArtifacts(arts, boardSize);
      const positions = {};
      const sizes = {};
      arts.forEach((a, idx) => {
        const L = layouts[idx];
        positions[a.id] = { x: L.x, y: L.y };
        sizes[a.id] = { width: L.width, height: L.height };
      });
      setCardPositions(positions);
      setCardSizes(sizes);
      return;
    }
    // Manual mode: cascade-place new artifacts off the most-recently-placed window
    // and pop them to the top of the stack so they aren't hidden behind existing ones.
    let assignedIds = [];
    setCardPositions(prev => {
      assignedIds = arts.filter(a => !prev[a.id]).map(a => a.id);
      if (assignedIds.length === 0) return prev;
      const next = { ...prev };
      const existing = Object.values(prev);
      const baseX = existing.length ? Math.max(...existing.map(p => p.x)) : 24;
      const baseY = existing.length ? Math.max(...existing.map(p => p.y)) : 24;
      const maxX = Math.max(320, (boardSize.width  || 900) - 320);
      const maxY = Math.max(180, (boardSize.height || 620) - 200);
      assignedIds.forEach((id, i) => {
        const off = (i + 1) * 32;
        next[id] = {
          x: Math.max(16, Math.min(maxX, baseX + off)),
          y: Math.max(16, Math.min(maxY, baseY + off)),
        };
      });
      return next;
    });
    if (assignedIds.length === 0) return;
    setCardSizes(prev => {
      const next = { ...prev };
      assignedIds.forEach(id => {
        if (!next[id]) next[id] = { width: 520, height: 380 };
      });
      return next;
    });
    setCardZ(prev => {
      const next = { ...prev };
      assignedIds.forEach(id => {
        nextZRef.current += 1;
        next[id] = nextZRef.current;
      });
      return next;
    });
    setSelectedArtifactId(assignedIds[assignedIds.length - 1]);
  }, [state, boardSize, layoutMode, minimizedIds, spaceArtifactIds]);

  useEffect(() => {
    if (!layoutHydratedRef.current) return;
    const t = setTimeout(() => {
      try {
        localStorage.setItem('madarx:artifact-layout-v2', JSON.stringify({
          positions: cardPositions,
          sizes: cardSizes,
          z: cardZ,
          minimized: minimizedIds,
          layoutMode,
          nextZ: nextZRef.current,
        }));
      } catch {}
    }, 200);
    return () => clearTimeout(t);
  }, [cardPositions, cardSizes, cardZ, minimizedIds, layoutMode]);

  return {
    selectedArtifactId,
    minimizedIds,
    cardZ,
    hoveredDockId,
    setHoveredDockId,
    fullscreenId,
    setFullscreenId,
    confirmDeleteId,
    setConfirmDeleteId,
    draggingId,
    resizingId,
    pointerDropSpaceId,
    layoutMode,
    setBoardEl,
    toggleMinimize,
    getLayout,
    bringToFront,
    resetArtifactLayout,
    deleteArtifactById,
    downloadArtifact,
    startDrag,
    startResize,
  };
}
