import { Dispatch, FC, ReactElement, PointerEvent as ReactPointerEvent, useCallback, useEffect, useMemo, useRef, useState, WheelEvent } from 'react';
import { FaCrosshairs, FaExpand, FaMinus, FaPlus, FaSyncAlt } from 'react-icons/fa';
import { tileToScreen, usePointerToTile } from '../hooks/usePointerToTile';
import { useTool } from '../hooks/useTool';
import { MAX_NUM_TILE_PER_AXIS, TILE_SIZE } from '../state/constants';
import { FloorplanAction, FloorplanState } from '../state/types';
import { FloorplanTile } from './FloorplanTile';
import { BaseButton } from '../../../common/BaseButton';
import { Hint } from '../../../common/Tooltip';
import { NativeTextTree } from '../../../common/native-text/NativeTextTree';

type Props = {
    state: FloorplanState;
    dispatch: Dispatch<FloorplanAction>;
    panMode?: boolean;
};

const VIEWBOX_W = 2048;
const VIEWBOX_H = (MAX_NUM_TILE_PER_AXIS * TILE_SIZE) / 2;

const ZOOM_MIN = 0.4;
const ZOOM_MAX = 6;
const ZOOM_STEP = 0.2;
const FIT_PADDING = TILE_SIZE * 2;

const GRID_FILL = '#242424';
const GRID_LINE = '#000000';

const clampZoom = (z: number): number => Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, z));

const computeRoomBounds = (state: FloorplanState): { x: number; y: number; w: number; h: number } | null => {
    let minX = Infinity;
    let minY = Infinity;
    let maxX = -Infinity;
    let maxY = -Infinity;
    let found = false;

    for (let r = 0; r < state.tiles.length; r++) {
        const row = state.tiles[r];

        if (!row) continue;

        for (let c = 0; c < row.length; c++) {
            const tile = row[c];

            if (!tile || tile.blocked) continue;

            const [x, y] = tileToScreen(r, c);
            const tileLeft = x - TILE_SIZE / 2;
            const tileRight = x + TILE_SIZE / 2;
            const tileTop = y;
            const tileBottom = y + TILE_SIZE / 2;

            if (tileLeft < minX) minX = tileLeft;
            if (tileRight > maxX) maxX = tileRight;
            if (tileTop < minY) minY = tileTop;
            if (tileBottom > maxY) maxY = tileBottom;
            found = true;
        }
    }

    if (!found) return null;

    return {
        x: minX - FIT_PADDING,
        y: minY - FIT_PADDING,
        w: maxX - minX + FIT_PADDING * 2,
        h: maxY - minY + FIT_PADDING * 2
    };
};

const GridPattern: FC<{ id: string }> = ({ id }) => {
    const w = TILE_SIZE;
    const h = TILE_SIZE / 2;
    const [originX, originY] = tileToScreen(0, 0);
    const diamond = (cx: number, cy: number) => `${cx},${cy - h / 2} ${cx + w / 2},${cy} ${cx},${cy + h / 2} ${cx - w / 2},${cy}`;

    return (
        <NativeTextTree>
            <defs>
                <pattern id={id} patternUnits="userSpaceOnUse" x={originX - w / 2} y={originY - h / 2} width={w} height={h}>
                    <rect width={w} height={h} fill={GRID_LINE} />
                    <polygon points={diamond(w / 2, h / 2)} fill={GRID_FILL} stroke={GRID_LINE} strokeWidth={1} />
                    <polygon points={diamond(0, 0)} fill={GRID_FILL} stroke={GRID_LINE} strokeWidth={1} />
                    <polygon points={diamond(w, 0)} fill={GRID_FILL} stroke={GRID_LINE} strokeWidth={1} />
                    <polygon points={diamond(0, h)} fill={GRID_FILL} stroke={GRID_LINE} strokeWidth={1} />
                    <polygon points={diamond(w, h)} fill={GRID_FILL} stroke={GRID_LINE} strokeWidth={1} />
                </pattern>
            </defs>
        </NativeTextTree>
    );
};

export const FloorplanCanvasSVG: FC<Props> = ({ state, dispatch, panMode }) => {
    const svgRef = useRef<SVGSVGElement | null>(null);
    const [zoom, setZoom] = useState(1);
    const [pan, setPan] = useState<{ x: number; y: number }>({ x: 0, y: 0 });
    const [isPanning, setIsPanning] = useState(false);
    const [flipped, setFlipped] = useState(false);
    const panStartRef = useRef<{ x: number; y: number; panX: number; panY: number } | null>(null);
    const centeredRef = useRef(false);

    const roomBounds = useMemo(() => computeRoomBounds(state), [state.tiles]);

    const centerPanForRoom = useCallback((): { x: number; y: number } | null => {
        if (!roomBounds) return null;
        return {
            x: roomBounds.x + roomBounds.w / 2 - VIEWBOX_W / 2,
            y: roomBounds.y + roomBounds.h / 2 - VIEWBOX_H / 2
        };
    }, [roomBounds]);

    const fitToRoom = useCallback(() => {
        if (!roomBounds) return;
        const zoomFitX = VIEWBOX_W / roomBounds.w;
        const zoomFitY = VIEWBOX_H / roomBounds.h;
        const targetZoom = clampZoom(Math.min(zoomFitX, zoomFitY) * 0.95);
        const next = centerPanForRoom();
        if (!next) return;
        setZoom(targetZoom);
        setPan(next);
    }, [roomBounds, centerPanForRoom]);

    useEffect(() => {
        if (centeredRef.current) return;
        if (!roomBounds) return;
        centeredRef.current = true;
        fitToRoom();
    }, [roomBounds, fitToRoom]);

    const visW = VIEWBOX_W / zoom;
    const visH = VIEWBOX_H / zoom;
    const baseX = (VIEWBOX_W - visW) / 2;
    const baseY = (VIEWBOX_H - visH) / 2;
    const viewX = baseX + pan.x;
    const viewY = baseY + pan.y;
    const viewBox = `${viewX} ${viewY} ${visW} ${visH}`;

    const projection = usePointerToTile(svgRef, { width: visW, height: visH, x: viewX, y: viewY });
    const tool = useTool(state, dispatch, projection);

    const rows = useMemo(
        () =>
            state.tiles.map((row, r) => {
                const cells = row.map((tile, c) => {
                    const isDoor = state.door.x === c && state.door.y === r && !tile.blocked;
                    const selected = state.selection.has(`${r},${c}`);
                    const south = state.tiles[r]?.[c + 1];
                    const west = state.tiles[r + 1]?.[c];
                    const southH = south && !south.blocked ? south.h : 0;
                    const westH = west && !west.blocked ? west.h : 0;
                    return <FloorplanTile key={`${r}-${c}`} row={r} col={c} tile={tile} selected={selected} isDoor={isDoor} southH={southH} westH={westH} />;
                });
                return <g key={`row-${r}`}>{cells}</g>;
            }),
        [state.tiles, state.door.x, state.door.y, state.selection]
    );

    const outOfBoundsOverlay = useMemo(() => {
        if (state.selection.size === 0) return null;
        const half = TILE_SIZE / 2;
        const quarter = TILE_SIZE / 4;
        const tilesRows = state.tiles.length;
        const tilesCols = state.tiles[0]?.length ?? 0;
        const out: ReactElement[] = [];
        for (const key of state.selection) {
            const [rStr, cStr] = key.split(',');
            const row = parseInt(rStr, 10);
            const col = parseInt(cStr, 10);
            if (row < tilesRows && col < tilesCols) continue;
            const [cx, cy] = tileToScreen(row, col);
            const points = `${cx},${cy - quarter} ${cx + half},${cy} ${cx},${cy + quarter} ${cx - half},${cy}`;
            out.push(<polygon key={`oob-${key}`} points={points} fill="rgba(250, 204, 21, 0.45)" stroke="#facc15" strokeWidth={1.5} strokeDasharray="3 2" />);
        }
        return out.length ? <g data-testid="selection-overlay">{out}</g> : null;
    }, [state.selection, state.tiles]);

    const zoomIn = useCallback(() => setZoom((z) => clampZoom(z + ZOOM_STEP)), []);
    const zoomOut = useCallback(() => setZoom((z) => clampZoom(z - ZOOM_STEP)), []);
    const resetView = useCallback(() => {
        fitToRoom();
    }, [fitToRoom]);

    const onWheel = useCallback((e: WheelEvent<SVGSVGElement>) => {
        if (!(e.ctrlKey || e.metaKey)) return;
        e.preventDefault();
        setZoom((z) => clampZoom(z + (e.deltaY < 0 ? ZOOM_STEP : -ZOOM_STEP)));
    }, []);

    useEffect(() => {
        const onMove = (e: PointerEvent) => {
            const start = panStartRef.current;
            if (!start) return;
            const dx = e.clientX - start.x;
            const dy = e.clientY - start.y;
            const rect = svgRef.current?.getBoundingClientRect();
            if (!rect) return;
            const scale = visW / rect.width;
            setPan({
                x: start.panX - dx * scale,
                y: start.panY - dy * scale
            });
        };
        const onUp = () => {
            panStartRef.current = null;
            setIsPanning(false);
        };
        window.addEventListener('pointermove', onMove);
        window.addEventListener('pointerup', onUp);
        return () => {
            window.removeEventListener('pointermove', onMove);
            window.removeEventListener('pointerup', onUp);
        };
    }, [visW]);

    const isPanGesture = (e: ReactPointerEvent): boolean => e.button === 1 || (e.button === 0 && e.shiftKey) || (e.button === 0 && Boolean(panMode));

    const cursorClass = isPanning ? 'cursor-grabbing' : panMode ? 'cursor-grab' : '';

    return (
        <NativeTextTree>
            <div className="fp-stage" data-testid="floorplan-2d">
                <svg
                    ref={svgRef}
                    viewBox={viewBox}
                    style={flipped ? { transform: 'scaleX(-1)' } : undefined}
                    className={`block w-full h-full select-none bg-black [shape-rendering:crispEdges] ${cursorClass}`}
                    onWheel={onWheel}
                    onPointerDown={(e) => {
                        if (isPanGesture(e)) {
                            e.preventDefault();
                            panStartRef.current = { x: e.clientX, y: e.clientY, panX: pan.x, panY: pan.y };
                            setIsPanning(true);
                            return;
                        }
                        tool.onPointerDown(e);
                    }}
                    onPointerMove={(e) => {
                        if (panStartRef.current) return;
                        tool.onPointerMove(e);
                    }}
                    onPointerUp={(e) => {
                        if (panStartRef.current) return;
                        tool.onPointerUp(e);
                    }}
                >
                    <GridPattern id="fp-grid" />
                    <rect data-testid="floorplan-grid" x={viewX} y={viewY} width={visW} height={visH} fill="url(#fp-grid)" />
                    {rows}
                    {outOfBoundsOverlay}
                </svg>
                <div className="fp-stage-buttons">
                    <Hint content="Fit room to view"><BaseButton aria-label="Fit room to view" type="button" data-testid="zoom-reset" className="fp-round-btn is-small" disabled={!roomBounds} onClick={resetView}>
                        <FaExpand size={10} />
                    </BaseButton></Hint>
                    <Hint content="Recenter on room (keep zoom)"><BaseButton aria-label="Recenter on room (keep zoom)"
                        type="button"
                        data-testid="zoom-recenter"
                        className="fp-round-btn is-small"
                        disabled={!roomBounds}
                        onClick={() => {
                            const next = centerPanForRoom();
                            if (next) setPan(next);
                        }}
                    >
                        <FaCrosshairs size={10} />
                    </BaseButton></Hint>
                    <Hint content={flipped ? 'Original view' : 'View from the other side'}>
                        <BaseButton aria-label={flipped ? 'Original view' : 'View from the other side'}
                            type="button"
                            data-testid="zoom-flip"
                            data-active={flipped ? 'true' : 'false'}
                            className={`fp-round-btn is-small ${flipped ? 'is-on' : ''}`}
                            onClick={() => setFlipped((v) => !v)}
                        >
                            <FaSyncAlt size={10} />
                        </BaseButton>
                    </Hint>
                    <Hint content={`Zoom in (Ctrl+wheel) · ${Math.round(zoom * 100)}%`}><BaseButton aria-label={`Zoom in (Ctrl+wheel) · ${Math.round(zoom * 100)}%`} type="button" data-testid="zoom-in" className="fp-round-btn" disabled={zoom >= ZOOM_MAX - 1e-3} onClick={zoomIn}>
                        <FaPlus size={13} />
                    </BaseButton></Hint>
                    <Hint content={`Zoom out (Ctrl+wheel) · ${Math.round(zoom * 100)}%`}><BaseButton aria-label={`Zoom out (Ctrl+wheel) · ${Math.round(zoom * 100)}%`} type="button" data-testid="zoom-out" className="fp-round-btn" disabled={zoom <= ZOOM_MIN + 1e-3} onClick={zoomOut}>
                        <FaMinus size={13} />
                    </BaseButton></Hint>
                </div>
            </div>
        </NativeTextTree>
    );
};
