import React, {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import { LayoutStore, LayoutCard } from './LayoutSystem';
import DesktopDocumentCard from './DesktopDocumentCard';
import usePreviewMetadata from './hooks/usePreviewMetadata';
import useDeskTagInteractions from './tags/useDeskTagInteractions';
import { useCardPointer } from './useCardPointer';
import '../styles/workspace/workspace-layout.css';
import '../styles/workspace/workspace-items.css';
import '../styles/workspace/workspace-cards.css';
import { useWorkspaceSelectionContext } from '../app/WorkspaceSelectionContext';
import { createDocumentEntryKey } from '../app/entryKey';
import { PointerTrackingProvider, usePointerTracking } from './PointerTrackingContext';
import type { Identifier } from '../types/identifiers';
import type { DocumentsListEntry, Document } from '../types/documents';
import { useAppState } from '../app/appState';
import { useDocumentOpen } from '../contexts/DocumentOpenContext';

type TagLike = { id?: Identifier | null; label?: string; color?: string | null } | null;
type OverlaySource = { url: string; alt?: string | null; mimeType?: string | null; };

export interface DeskDocument {
  id?: Identifier | null;
  title?: string;
  tags?: TagLike[] | null;
  documentLink?: OverlaySource | null;
  [key: string]: unknown;
}

interface DocumentSizeInfo {
  width: number;
  height: number;
  source?: 'snapshot' | 'metadata' | 'fallback';
}

// Fallback size computation
const computeFallbackCardSize = (_doc: DeskDocument, defaultSize: number = 200): DocumentSizeInfo => {
  const size = Math.round(defaultSize * (1 / Math.SQRT2));
  return { width: size, height: size, source: 'fallback' };
};

export interface DesktopWorkspaceProps {
  entries: DocumentsListEntry[];
  ensureAssetUrl?: (...args: any[]) => Promise<unknown>;
  getDocumentAsset?: (...args: any[]) => unknown;
  onSelectionChange?: (selectedIds: Identifier[]) => void;
  onDocumentTagAttach?: (docId: Identifier, tagId: Identifier) => void;
  onDocumentTagDetach?: (docId: Identifier, tagId: Identifier) => void;
  viewId?: string | null;
  defaultCardSize?: number;
}

// Wrapper to handle hooks per card
const DesktopDocumentContainer: React.FC<React.ComponentProps<typeof DesktopDocumentCard> & {
  onSelect: (ids: string[], extend?: boolean) => void;
  onDeselect: (ids: string[]) => void;
  onDocumentActivate?: (id: string, event?: any) => void;
  selection: string[];
  requestCanvasFocus?: () => void;
}> = React.memo((props) => {
  const { layoutCard, selected, onSelect, onDeselect, onDocumentActivate, selection, requestCanvasFocus } = props;

  // We assume layoutCard is always present in this context
  const cardPointerHandlers = useCardPointer(layoutCard!, !!selected, selection, onSelect, onDeselect, onDocumentActivate, requestCanvasFocus);

  return (
    <DesktopDocumentCard
      {...props}
      cardPointerHandlers={cardPointerHandlers}
    />
  );
});

DesktopDocumentContainer.displayName = 'DesktopDocumentContainer';

const DesktopWorkspaceContent: React.FC<DesktopWorkspaceProps> = ({
  entries,
  ensureAssetUrl,
  getDocumentAsset,
  onSelectionChange,
  onDocumentTagAttach,
  onDocumentTagDetach,
  viewId,
  defaultCardSize = 200,
}) => {
  const { openDocument } = useDocumentOpen();
  const { tenant } = useAppState();
  const tenantId = tenant?.id as Identifier;
  const { addPointer, removePointer } = usePointerTracking();
  const containerRef = useRef<HTMLDivElement>(null);
  const [isLayoutReady, setIsLayoutReady] = useState(false);
  const [isLayoutLoaded, setIsLayoutLoaded] = useState(false);
  const [hasContainerSize, setHasContainerSize] = useState(false);

  const items = useMemo(() => {
    return entries
      .filter((entry): entry is { type: 'document'; document: Document } & DocumentsListEntry =>
        entry.type === 'document' && !!entry.document
      )
      .map(entry => entry.document as DeskDocument);
  }, [entries]);

  // Layout System Initialization
  const layoutStore = useMemo(() => new LayoutStore(), []);
  const layoutRef = useRef<Map<string, LayoutCard>>(new Map());

  // Update container size in store
  useEffect(() => {
    if (!containerRef.current) return;

    const observer = new ResizeObserver((entries) => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect;
        layoutStore.setContainerSize(width, height);
        if (width > 0 && height > 0) {
          setHasContainerSize(true);
        }
      }
    });

    observer.observe(containerRef.current);
    return () => observer.disconnect();
  }, [layoutStore]);

  useEffect(() => {
    if (tenantId && viewId) {
      // Clear store when switching views to prevent stale items
      layoutStore.clear();
      setIsLayoutLoaded(false);
      setIsLayoutReady(false); // Immediately hide cards during transition
      layoutStore.loadLayout(String(tenantId), viewId).then(() => {
        setIsLayoutLoaded(true);
      });
    } else {
      // No tenant/view ID means no saved layout to load - skip directly to loaded
      setIsLayoutLoaded(true);
    }
  }, [layoutStore, tenantId, viewId]);

  // Only set layout ready when both container has size AND layout is loaded
  useEffect(() => {
    setIsLayoutReady(hasContainerSize && isLayoutLoaded);
  }, [hasContainerSize, isLayoutLoaded]);

  // Sync LayoutStore items with current entries to remove stale items
  useEffect(() => {
    const currentIds = new Set(items.map((doc, index) => doc.id ? String(doc.id) : `temp-${index}`));

    // Identify and remove items that are no longer present
    for (const id of layoutStore.items.keys()) {
      if (!currentIds.has(id)) {
        layoutStore.unregister(id);
      }
    }
  }, [items, layoutStore]);

  // Selection Context
  const {
    selectedDocumentIds,
    setSelectedEntries,
    clearSelection,
  } = useWorkspaceSelectionContext();

  const handleSelectionChange = useCallback((ids: Identifier[]) => {
    // Sort IDs by Z-index (ascending) so the last item is the top-most
    const sortedIds = [...ids].sort((a, b) => {
      const cardA = layoutStore.items.get(String(a));
      const cardB = layoutStore.items.get(String(b));
      const zA = cardA ? cardA.z : -Infinity;
      const zB = cardB ? cardB.z : -Infinity;
      return zA - zB;
    });

    if (setSelectedEntries) {
      const keys = sortedIds.map(id => createDocumentEntryKey(id));
      setSelectedEntries(keys);
    }
    onSelectionChange?.(sortedIds);
  }, [setSelectedEntries, onSelectionChange, layoutStore]);

  const onClearSelection = useCallback(() => {
    clearSelection ? clearSelection() : handleSelectionChange([]);
  }, [clearSelection, handleSelectionChange]);

  const metadataMap = usePreviewMetadata(items, getDocumentAsset, ensureAssetUrl);

  const ensureDocumentSize = useCallback((doc: DeskDocument): DocumentSizeInfo => {
    if (doc.id) {
      const meta = metadataMap.get(String(doc.id));
      if (meta && meta.width && meta.height)
        return { width: meta.width, height: meta.height, source: 'metadata' };
    }

    return computeFallbackCardSize(doc, defaultCardSize);
  }, [metadataMap, defaultCardSize]);

  // Sync LayoutStore to layoutRef
  useEffect(() => {
    const sync = () => {
      layoutRef.current = layoutStore.items;
    };
    sync();
  }, [layoutStore.items]);

  const handleShellKeyDown = useCallback(() => { }, []);
  const focusShell = useCallback(() => {
    if (containerRef.current) {
      containerRef.current.focus();
    }
  }, []);

  // Tag Interactions
  const tagInteractions = useDeskTagInteractions({
    onAssignTagToDocument: (docId: string, tagId: string) => {
      onDocumentTagAttach?.(docId, tagId);
    },
    onRemoveTagFromDocument: (docId: string, tagId: string) => {
      onDocumentTagDetach?.(docId, tagId);
    },
    requestCanvasFocus: focusShell,
  });

  useEffect(() => {
    const handleWindowKeyDown = (e: KeyboardEvent) => {
      // Only handle events if the container itself is the target (focused)
      if (e.target !== containerRef.current) {
        return;
      }

      // Space preview logic
      if (e.code === 'Space' && selectedDocumentIds.length > 0) {
        const lastId = selectedDocumentIds[selectedDocumentIds.length - 1];
        const doc = items.find(i => String(i.id) === lastId);
        if (doc) {
          e.preventDefault();
          openDocument(doc, 'preview');
          return;
        }
      }

      // Navigation logic
      if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
        e.preventDefault();

        const layoutItems = Array.from(layoutStore.items.values()) as LayoutCard[];
        if (layoutItems.length === 0) return;

        let activeCard = null;
        if (selectedDocumentIds.length > 0) {
          // Use the last selected item as the anchor
          const lastId = selectedDocumentIds[selectedDocumentIds.length - 1];
          activeCard = layoutStore.items.get(lastId);
        }

        // If no selection or active card not found, select the top-most item
        if (!activeCard) {
          const topMost = layoutItems.reduce((prev, current) => (prev.z > current.z ? prev : current));
          handleSelectionChange([topMost.id]);
          return;
        }

        const cx = activeCard.centerX;
        const cy = activeCard.centerY;

        let bestCandidate = null;
        let minScore = Infinity;

        for (const candidate of layoutItems) {
          if (candidate.id === activeCard.id) continue;

          const dx = candidate.centerX - cx;
          const dy = candidate.centerY - cy;

          let valid = false;
          let primaryDist = 0;
          let offAxisDist = 0;

          switch (e.key) {
            case 'ArrowRight':
              if (dx > 0 && dx > Math.abs(dy)) {
                valid = true;
                primaryDist = dx;
                offAxisDist = Math.abs(dy);
              }
              break;
            case 'ArrowLeft':
              if (dx < 0 && -dx > Math.abs(dy)) {
                valid = true;
                primaryDist = -dx;
                offAxisDist = Math.abs(dy);
              }
              break;
            case 'ArrowDown':
              if (dy > 0 && dy > Math.abs(dx)) {
                valid = true;
                primaryDist = dy;
                offAxisDist = Math.abs(dx);
              }
              break;
            case 'ArrowUp':
              if (dy < 0 && -dy > Math.abs(dx)) {
                valid = true;
                primaryDist = -dy;
                offAxisDist = Math.abs(dx);
              }
              break;
          }

          if (valid) {
            // Weighted score: favor items closer in the primary direction, penalize off-axis
            // We use a multiplier for off-axis distance to prefer "straighter" lines
            // Reduced off-axis weight to favor directional distance (grid-like behavior)
            let score = primaryDist + (offAxisDist * 0.2);

            // Z-Order Bonus: Subtract a small value based on Z-index to favor higher items
            // Assuming max Z is around 10000, 0.1 gives a max bonus of 1000, which is significant but less than primary distance usually
            score -= (candidate.z * 0.05);

            // Obstruction Penalty: Check if the candidate is obstructed
            // If less than 5% is visible, treat as obstructed
            if (candidate.getVisibleFraction() < 0.05) {
              score += 5000; // Huge penalty for obstructed items
            }

            if (score < minScore) {
              minScore = score;
              bestCandidate = candidate;
            }
          }
        }

        if (bestCandidate) {
          if (e.shiftKey) {
            // Additive selection
            const newSelection = new Set(selectedDocumentIds);
            newSelection.add(bestCandidate.id);
            handleSelectionChange(Array.from(newSelection));
          } else {
            // Replace selection
            handleSelectionChange([bestCandidate.id]);
          }
        }
      }
    };

    window.addEventListener('keydown', handleWindowKeyDown);
    return () => window.removeEventListener('keydown', handleWindowKeyDown);
  }, [selectedDocumentIds, items, openDocument, layoutStore, handleSelectionChange]);

  return (
    <>
      <div
        className="desk-shell"
      >
        <div
          className="desk-canvas"
          ref={containerRef}
          tabIndex={0}
          onKeyDown={handleShellKeyDown}
          onPointerDown={(e) => {
            if (e.target === e.currentTarget) {
              // Register background pointer
              addPointer(e.pointerId);
              (e.target as Element).setPointerCapture(e.pointerId);

              onClearSelection();
              focusShell();
            }
          }}
          onPointerUp={(e) => {
            if (e.target === e.currentTarget) {
              removePointer(e.pointerId);
              (e.target as Element).releasePointerCapture(e.pointerId);
            }
          }}
          onPointerCancel={(e) => {
            if (e.target === e.currentTarget) {
              removePointer(e.pointerId);
              (e.target as Element).releasePointerCapture(e.pointerId);
            }
          }}
          onDrop={tagInteractions.handleCanvasDrop}
          onDragOver={tagInteractions.handleCanvasDragOver}
        >
          {isLayoutReady && items.map((doc, index) => {
            const docId = doc.id ? String(doc.id) : `temp-${index}`;
            const isSelected = selectedDocumentIds.includes(docId);
            const size = ensureDocumentSize(doc);

            // Extract page count
            const metadata = doc.current_version?.metadata as { page_count?: number } | undefined;
            const pageCount = metadata?.page_count ?? 1;

            const layoutCard = layoutStore.initialize(docId, null, {
              width: size.width,
              height: size.height,
              pageCount,
              maxSize: defaultCardSize
            });

            return (
              <DesktopDocumentContainer
                key={docId}
                doc={doc}
                style={{
                  position: 'absolute',
                  top: 0,
                  left: 0,
                  touchAction: 'none',
                  willChange: 'transform'
                }}
                shouldLoad={true}
                matchesFilter={true}

                selected={isSelected}
                docTagTokens=""
                ensureAssetUrl={ensureAssetUrl}
                getDocumentAsset={getDocumentAsset}
                handleNavigatorSnapshot={() => { }}
                onDocumentActivate={(_id, event) => {
                  const isPreview = event && ((event as any).altKey || (event as any).button === 1);
                  openDocument(doc, isPreview ? 'preview' : 'sidepanel');
                }}
                layoutCard={layoutCard}
                onTagDragEnter={tagInteractions.handleTagDragEnterDoc}
                onTagDragOver={tagInteractions.handleTagDragOverDoc}
                onTagDragLeave={tagInteractions.handleTagDragLeaveDoc}
                onTagDrop={tagInteractions.handleTagDropOnDoc}
                onDocTagDragStart={tagInteractions.handleDocTagDragStart}
                onDocTagDragEnd={tagInteractions.handleDocTagDragEnd}
                onSelect={(ids, extend = false) => {
                  if (!extend) {
                    handleSelectionChange(ids);
                  } else {
                    const newSelection = new Set(selectedDocumentIds);
                    ids.forEach(id => newSelection.add(id));
                    handleSelectionChange(Array.from(newSelection));
                  }
                }}
                onDeselect={(ids) => {
                  const newSelection = new Set(selectedDocumentIds);
                  ids.forEach(id => newSelection.delete(id));
                  handleSelectionChange(Array.from(newSelection));
                }}
                selection={selectedDocumentIds}
                requestCanvasFocus={focusShell}
              />
            );
          })}
        </div>
      </div>
    </>
  );
};

const DesktopWorkspace: React.FC<DesktopWorkspaceProps> = (props) => {
  return (
    <PointerTrackingProvider>
      <DesktopWorkspaceContent {...props} />
    </PointerTrackingProvider>
  );
};

export default DesktopWorkspace;
