import { Suspense, useEffect, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { flattenObjects } from '@tts/extract';
import type { TTSObject } from '@tts/shared';
import { useModStore } from '../stores/modStore';
import { useSearchStore } from '../stores/searchStore';
import Scene from '../components/viewers/Scene';
import { TileObjectMesh } from '../components/viewers/TileViewer';
import { TokenObjectMesh } from '../components/viewers/TokenViewer';
import { CardObjectMesh } from '../components/viewers/CardViewer';
import { CustomModelMesh } from '../components/viewers/CustomModelViewer';
import { objectPlacement } from '../components/viewers/transform';

/** Object classes that carry renderable assets (mirrors `viewers/register.ts`). */
const RENDERABLE = new Set([
  'Tile',
  'Custom_Tile',
  'Custom_Token',
  'Card',
  'CardCustom',
  'Deck',
  'DeckCustom',
  'Custom_Deck',
  'Custom_Model',
  'Custom_Model_Bag',
  'Custom_Model_Infinite_Bag',
]);

/**
 * A "full setup" view: renders every loadable object in a save into a single
 * shared scene, placed at its real position from the save's `Transform`.
 * Geometry and materials are shared across objects (see
 * `components/viewers/sharedResources.ts`).
 */
export default function FullSetupPage() {
  const { id } = useParams<{ id: string }>();
  const { mod, loading, error, load } = useModStore();
  const item = useSearchStore((s) => s.items.find((i) => i.id === id));

  useEffect(() => {
    if (id) load(id, item?.fileUrl);
  }, [id, item?.fileUrl, load]);

  const objects = useMemo(
    () => (mod ? flattenObjects(mod) : []),
    [mod],
  );

  const renderable = useMemo(
    () => objects.filter((o) => RENDERABLE.has(o.Name)),
    [objects],
  );
  const skipped = objects.length - renderable.length;

  const placed = useMemo(
    () =>
      renderable
        .map((object) => ({ object, placement: objectPlacement(object) }))
        .filter(
          (p): p is { object: TTSObject; placement: NonNullable<ReturnType<typeof objectPlacement>> } =>
            p.placement !== null,
        ),
    [renderable],
  );

  if (loading) return <p className="text-sm text-zinc-400">Loading mod…</p>;
  if (error) return <p className="text-sm text-red-400">{error}</p>;
  if (!mod) return <p className="text-sm text-zinc-500">No mod loaded.</p>;

  return (
    <div className="space-y-6">
      <div>
        <h1 className="text-2xl font-semibold">Full setup</h1>
        <p className="mt-1 text-sm text-zinc-400">
          {objects.length} objects · {renderable.length} rendered
          {skipped > 0 ? ` · ${skipped} skipped` : ''}
        </p>
      </div>

      <Scene>
        <Suspense fallback={null}>
          {/* Key by index, not GUID: cards in a deck share the deck's GUID. */}
          {placed.map(({ object, placement }, index) => (
            <group
              key={index}
              position={placement.position}
              rotation={placement.rotation}
              scale={placement.scale}
            >
              <group rotation={placement.layFlat}>
                <RenderObject object={object} />
              </group>
            </group>
          ))}
        </Suspense>
      </Scene>
    </div>
  );
}

/** Dispatch a renderable object to the mesh component for its class. */
function RenderObject({ object }: { object: TTSObject }) {
  switch (object.Name) {
    case 'Tile':
    case 'Custom_Tile':
      return <TileObjectMesh object={object} />;
    case 'Custom_Token':
      return <TokenObjectMesh object={object} />;
    case 'Card':
    case 'CardCustom':
    case 'Deck':
    case 'DeckCustom':
    case 'Custom_Deck':
      return <CardObjectMesh object={object} />;
    case 'Custom_Model':
    case 'Custom_Model_Bag':
    case 'Custom_Model_Infinite_Bag':
      return <CustomModelMesh object={object} />;
    default:
      return null;
  }
}