# Phase 3: Hooks Extraction

## Goal

Extract state management logic into reusable custom hooks.

## Files to Create

### hooks/useImageTransform.ts

Manages rotation and flip transformations.

```typescript
'use client';

/**
 * useImageTransform - Manages image rotation and flip state
 */

import { useState, useCallback, useEffect, useMemo } from 'react';
import type { ImageTransform } from '../types';
import { DEFAULT_TRANSFORM } from '../utils';

export interface UseImageTransformOptions {
  /** Reset transform when this key changes */
  resetKey?: string;
}

export interface UseImageTransformReturn {
  /** Current transform state */
  transform: ImageTransform;
  /** Rotate 90 degrees clockwise */
  rotate: () => void;
  /** Toggle horizontal flip */
  flipH: () => void;
  /** Toggle vertical flip */
  flipV: () => void;
  /** Reset all transforms */
  reset: () => void;
  /** CSS transform string for applying to image */
  transformStyle: string;
}

export function useImageTransform(
  options: UseImageTransformOptions = {}
): UseImageTransformReturn {
  const { resetKey } = options;

  const [transform, setTransform] = useState<ImageTransform>(DEFAULT_TRANSFORM);

  // Reset transform when key changes
  useEffect(() => {
    if (resetKey) {
      setTransform(DEFAULT_TRANSFORM);
    }
  }, [resetKey]);

  const rotate = useCallback(() => {
    setTransform((prev) => ({
      ...prev,
      rotation: (prev.rotation + 90) % 360,
    }));
  }, []);

  const flipH = useCallback(() => {
    setTransform((prev) => ({
      ...prev,
      flipH: !prev.flipH,
    }));
  }, []);

  const flipV = useCallback(() => {
    setTransform((prev) => ({
      ...prev,
      flipV: !prev.flipV,
    }));
  }, []);

  const reset = useCallback(() => {
    setTransform(DEFAULT_TRANSFORM);
  }, []);

  // Build CSS transform string
  const transformStyle = useMemo(() => {
    const transforms: string[] = [];

    if (transform.rotation !== 0) {
      transforms.push(`rotate(${transform.rotation}deg)`);
    }
    if (transform.flipH) {
      transforms.push('scaleX(-1)');
    }
    if (transform.flipV) {
      transforms.push('scaleY(-1)');
    }

    return transforms.join(' ');
  }, [transform]);

  return {
    transform,
    rotate,
    flipH,
    flipV,
    reset,
    transformStyle,
  };
}
```

### hooks/useImageLoading.ts

Manages image loading, blob URLs, and LQIP generation.

```typescript
'use client';

/**
 * useImageLoading - Manages image loading state with LQIP
 */

import { useState, useEffect, useRef } from 'react';
import { useImageCache, generateContentKey } from '../../../stores/mediaCache';
import { createLQIP, MAX_IMAGE_SIZE, WARN_IMAGE_SIZE } from '../utils';

export interface UseImageLoadingOptions {
  /** Image content (ArrayBuffer or string) */
  content: string | ArrayBuffer;
  /** MIME type for blob creation */
  mimeType?: string;
}

export interface UseImageLoadingReturn {
  /** Blob URL source for the image */
  src: string | null;
  /** Low-quality placeholder URL */
  lqip: string | null;
  /** Whether image is loading */
  isLoading: boolean;
  /** Whether high-quality image is ready */
  isReady: boolean;
  /** Error message if any */
  error: string | null;
  /** Content key for caching */
  contentKey: string;
  /** Image size in bytes */
  size: number;
  /** Whether image exceeds size limit */
  isOversized: boolean;
  /** Whether image exceeds warning threshold */
  isLarge: boolean;
}

export function useImageLoading(
  options: UseImageLoadingOptions
): UseImageLoadingReturn {
  const { content, mimeType } = options;

  const { getOrCreateBlobUrl, releaseBlobUrl } = useImageCache();

  const [src, setSrc] = useState<string | null>(null);
  const [lqip, setLqip] = useState<string | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [isReady, setIsReady] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const contentKeyRef = useRef<string>('');

  // Calculate size and limits
  const size = content instanceof ArrayBuffer
    ? content.byteLength
    : content.length;
  const isOversized = size > MAX_IMAGE_SIZE;
  const isLarge = size > WARN_IMAGE_SIZE;

  // Generate content key
  const contentKey = generateContentKey(content);
  contentKeyRef.current = contentKey;

  // Create blob URL and LQIP
  useEffect(() => {
    if (isOversized) {
      setError(`Image too large (${(size / 1024 / 1024).toFixed(1)}MB). Maximum: 50MB`);
      setIsLoading(false);
      return;
    }

    setIsLoading(true);
    setError(null);
    setIsReady(false);
    setLqip(null);

    // Create blob URL
    const blobUrl = getOrCreateBlobUrl(content, mimeType);
    setSrc(blobUrl);

    // Generate LQIP for progressive loading
    if (isLarge) {
      createLQIP(blobUrl).then((placeholder) => {
        if (contentKeyRef.current === contentKey) {
          setLqip(placeholder);
        }
      });
    }

    // Preload full image
    const img = new Image();
    img.onload = () => {
      if (contentKeyRef.current === contentKey) {
        setIsReady(true);
        setIsLoading(false);
      }
    };
    img.onerror = () => {
      if (contentKeyRef.current === contentKey) {
        setError('Failed to load image');
        setIsLoading(false);
      }
    };
    img.src = blobUrl;

    // Cleanup
    return () => {
      releaseBlobUrl(blobUrl);
    };
  }, [content, contentKey, mimeType, isOversized, isLarge, size, getOrCreateBlobUrl, releaseBlobUrl]);

  return {
    src,
    lqip,
    isLoading,
    isReady,
    error,
    contentKey,
    size,
    isOversized,
    isLarge,
  };
}
```

### hooks/index.ts

```typescript
/**
 * ImageViewer hooks - Public API
 */

export { useImageTransform } from './useImageTransform';
export type { UseImageTransformOptions, UseImageTransformReturn } from './useImageTransform';

export { useImageLoading } from './useImageLoading';
export type { UseImageLoadingOptions, UseImageLoadingReturn } from './useImageLoading';
```

## Benefits

1. **Reusable** - Hooks can be used in other components
2. **Testable** - Easy to unit test state logic
3. **Clean** - Main component becomes much simpler
4. **Separated** - Transform logic independent of loading logic
