"use client";

import { type CSSProperties, useEffect, useRef, forwardRef, useImperativeHandle } from "react";
import "maplibre-gl/dist/maplibre-gl.css";

import { Feature, FeatureCollection, Geometry } from "@/uhm/lib/editor/state/useEditorState";
import { BackgroundLayerVisibility } from "@/uhm/lib/map/styles/backgroundLayers";
import type { EditorMode } from "@/uhm/lib/editor/session/sessionTypes";

import { useMapInstance } from "./map/useMapInstance";
import { setupMapLayers } from "./map/useMapLayers";
import { useMapInteraction } from "./map/useMapInteraction";
import { useMapSync } from "./map/useMapSync";
import { bindImageOverlayInteractions, type MapImageOverlay } from "./map/imageOverlay";

export type MapHoverPayload = {
    featureId: string | number;
    feature: Feature | null;
    point: { x: number; y: number };
    lngLat: { lng: number; lat: number };
};

export type MapHandle = {
    getViewState: () => {
        center: { lng: number; lat: number };
        zoom: number;
        pitch: number;
        bearing: number;
        projection: string;
    } | null;
    getMap: () => import("maplibre-gl").Map | null;
};

type MapProps = {
    mode: EditorMode;
    // FeatureCollection that should actually be rendered/interacted with on the map.
    // Callers should apply timeline/replay filters before passing it here.
    renderDraft: FeatureCollection;
    backgroundVisibility: BackgroundLayerVisibility;
    geometryVisibility?: Record<string, boolean>;
    selectedFeatureIds: (string | number)[];
    onSelectFeatureIds: (ids: (string | number)[]) => void;
    onSetMode?: (mode: EditorMode, featureId?: string | number) => void;
    // Label lookup context only. It may include non-rendered geometries for entity label resolution.
    labelContextDraft?: FeatureCollection;
    labelTimelineYear?: number | null;
    onCreateFeature?: (feature: FeatureCollection["features"][number]) => void;
    onDeleteFeature?: (id: string | number | (string | number)[]) => void;
    onHideFeature?: (id: string | number) => void;
    onUpdateFeature?: (id: string | number, geometry: Geometry) => void;
    allowGeometryEditing?: boolean;
    applyGeometryBindingFilter?: boolean;
    height?: CSSProperties["height"];
    fitToDraftBounds?: boolean;
    fitBoundsKey?: string | number | null;
    onHoverFeatureChange?: ((payload: MapHoverPayload | null) => void) | undefined;
    highlightFeatures?: FeatureCollection | null;
    focusFeatureCollection?: FeatureCollection | null;
    focusRequestKey?: string | number | null;
    focusPadding?: number | import("maplibre-gl").PaddingOptions;
    imageOverlay?: MapImageOverlay | null;
    onImageOverlayChange?: (overlay: MapImageOverlay) => void;
    onBindGeometries?: (targetId: string | number, sourceIds: (string | number)[]) => void;
};

const Map = forwardRef<MapHandle, MapProps>(function Map({
    mode,
    onSetMode,
    renderDraft,
    backgroundVisibility,
    geometryVisibility,
    selectedFeatureIds,
    onSelectFeatureIds,
    labelContextDraft,
    labelTimelineYear,
    onCreateFeature,
    onDeleteFeature,
    onHideFeature,
    onUpdateFeature,
    allowGeometryEditing = true,
    applyGeometryBindingFilter = true,
    height = "100vh",
    fitToDraftBounds = false,
    fitBoundsKey = null,
    onHoverFeatureChange,
    highlightFeatures = null,
    focusFeatureCollection = null,
    focusRequestKey = null,
    focusPadding,
    imageOverlay = null,
    onImageOverlayChange,
    onBindGeometries,
}, ref) {
    // Ref giữ mode mới nhất cho MapLibre handlers được register một lần.
    const modeRef = useRef<MapProps["mode"]>(mode);
    // Ref giữ render draft mới nhất để map engines đọc không bị stale closure.
    const renderDraftRef = useRef<FeatureCollection>(renderDraft);
    // Ref callback select feature mới nhất cho event click trên map.
    const onSelectFeatureIdsRef = useRef(onSelectFeatureIds);
    // Ref callback đổi mode mới nhất, dùng khi map interaction chuyển sang replay/select.
    const onSetModeRef = useRef(onSetMode);
    // Ref callback hover mới nhất cho tooltip/panel ngoài map.
    const onHoverFeatureChangeRef = useRef<MapProps["onHoverFeatureChange"]>(onHoverFeatureChange);
    // Ref callback create mới nhất khi drawing engine tạo feature.
    const onCreateRef = useRef<MapProps["onCreateFeature"]>(onCreateFeature);
    // Ref callback delete mới nhất khi editing engine xóa feature.
    const onDeleteRef = useRef<MapProps["onDeleteFeature"]>(onDeleteFeature);
    // Ref callback hide local mới nhất khi context menu select ẩn feature khỏi map.
    const onHideRef = useRef<MapProps["onHideFeature"]>(onHideFeature);
    // Ref callback update mới nhất khi editing engine đổi geometry.
    const onUpdateRef = useRef<MapProps["onUpdateFeature"]>(onUpdateFeature);
    // Ref giữ overlay mới nhất cho right-drag controls.
    const imageOverlayRef = useRef<MapImageOverlay | null>(imageOverlay);
    // Ref callback update overlay mới nhất để interaction không stale.
    const onImageOverlayChangeRef = useRef<MapProps["onImageOverlayChange"]>(onImageOverlayChange);
    // Ref callback bind geometry mới nhất để interaction không stale.
    const onBindGeometriesRef = useRef<MapProps["onBindGeometries"]>(onBindGeometries);
 
    useEffect(() => { modeRef.current = mode; }, [mode]);
    useEffect(() => { renderDraftRef.current = renderDraft; }, [renderDraft]);
    useEffect(() => { onSelectFeatureIdsRef.current = onSelectFeatureIds; }, [onSelectFeatureIds]);
    useEffect(() => { onSetModeRef.current = onSetMode; }, [onSetMode]);
    useEffect(() => { onHoverFeatureChangeRef.current = onHoverFeatureChange; }, [onHoverFeatureChange]);
    useEffect(() => { onCreateRef.current = onCreateFeature; }, [onCreateFeature]);
    useEffect(() => { onDeleteRef.current = onDeleteFeature; }, [onDeleteFeature]);
    useEffect(() => { onHideRef.current = onHideFeature; }, [onHideFeature]);
    useEffect(() => { onUpdateRef.current = onUpdateFeature; }, [onUpdateFeature]);
    useEffect(() => { imageOverlayRef.current = imageOverlay; }, [imageOverlay]);
    useEffect(() => { onImageOverlayChangeRef.current = onImageOverlayChange; }, [onImageOverlayChange]);
    useEffect(() => { onBindGeometriesRef.current = onBindGeometries; }, [onBindGeometries]);

    // Hook sở hữu lifecycle MapLibre instance và các control camera/projection.
    const {
        mapRef,
        containerRef,
        fatalInitError,
        zoomLevel,
        zoomBounds,
        isGlobeProjection,
        setIsGlobeProjection,
        isMapLoaded,
        geolocationCenteredRef,
        handleZoomByStep,
        handleZoomSliderChange,
        beginZoomSliderDrag,
        endZoomSliderDrag,
        getViewState,
    } = useMapInstance();

    // Public API cho parent đọc map instance/view state mà không expose implementation nội bộ.
    useImperativeHandle(ref, () => ({
        getViewState,
        getMap: () => mapRef.current,
    }), [getViewState, mapRef]);

    // Hook gắn/dọn các interaction vẽ, chọn, sửa geometry.
    const {
        editingEngineRef,
        setupMapInteractions,
        cleanupMapInteractions,
    } = useMapInteraction({
        mapRef,
        mode,
        modeRef,
        renderDraftRef,
        allowGeometryEditing,
        selectedFeatureIds,
        onSelectFeatureIdsRef,
        onSetModeRef,
        onCreateRef,
        onDeleteRef,
        onHideRef,
        onUpdateRef,
        onHoverFeatureChangeRef,
        onBindGeometriesRef,
    });

    // Hook đồng bộ draft/layer/filter/highlight từ React state xuống MapLibre source/layer.
    const {
        applyRenderDraftToMap,
        applyHighlightToMap,
        applyImageOverlayToMap,
        tryCenterToUserLocation,
    } = useMapSync({
        mapRef,
        renderDraft,
        labelContextDraft,
        labelTimelineYear,
        backgroundVisibility,
        geometryVisibility,
        selectedFeatureIds,
        applyGeometryBindingFilter,
        fitToDraftBounds,
        fitBoundsKey,
        highlightFeatures,
        focusFeatureCollection,
        focusRequestKey,
        focusPadding,
        imageOverlay,
        allowGeometryEditing,
        editingEngineRef,
        geolocationCenteredRef,
    });

    useEffect(() => {
        const map = mapRef.current;
        if (!map || !isMapLoaded) return;

        setupMapLayers(map, backgroundVisibility, highlightFeatures, applyHighlightToMap);
        applyImageOverlayToMap();
        setupMapInteractions(map);
        applyRenderDraftToMap(renderDraftRef.current);
        tryCenterToUserLocation();

        return () => {
            cleanupMapInteractions();
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [isMapLoaded]);

    useEffect(() => {
        const map = mapRef.current;
        if (map && isMapLoaded) {
            // Trigger resize after a short delay to allow layout to settle
            setTimeout(() => map.resize(), 100);
        }
    }, [mode, isMapLoaded, mapRef]);

    const hasImageOverlay = Boolean(imageOverlay);
    useEffect(() => {
        const map = mapRef.current;
        if (!map || !isMapLoaded || !hasImageOverlay) return;
        return bindImageOverlayInteractions(
            map,
            () => imageOverlayRef.current,
            (nextOverlay) => onImageOverlayChangeRef.current?.(nextOverlay)
        );
    }, [hasImageOverlay, isMapLoaded, mapRef]);

    return (
        <div style={{ width: "100%", height, position: "relative" }}>
            <div ref={containerRef} style={{ width: "100%", height: "100%" }} />

            {fatalInitError ? (
                <div
                    style={{
                        position: "absolute",
                        inset: 0,
                        zIndex: 50,
                        display: "grid",
                        placeItems: "center",
                        padding: "24px",
                        background: "rgba(2, 6, 23, 0.78)",
                        color: "#e2e8f0",
                    }}
                >
                    <div
                        style={{
                            maxWidth: "680px",
                            border: "1px solid rgba(148, 163, 184, 0.3)",
                            borderRadius: "12px",
                            background: "rgba(15, 23, 42, 0.92)",
                            padding: "14px 16px",
                        }}
                    >
                        <div style={{ fontWeight: 800, marginBottom: "6px" }}>
                            Map khong khoi tao duoc
                        </div>
                        <div style={{ color: "#cbd5e1", fontSize: "13px" }}>
                            {fatalInitError}
                        </div>
                    </div>
                </div>
            ) : null}

            <div
                style={{
                    position: "absolute",
                    top: "10px",
                    left: "16px",
                    right: "16px",
                    zIndex: 12,
                    pointerEvents: "none",
                }}
            >
                <div
                    style={{
                        maxWidth: "650px",
                        margin: "0 auto",
                        display: "flex",
                        alignItems: "center",
                        gap: "10px",
                        background: "rgba(15, 23, 42, 0.88)",
                        border: "1px solid rgba(148, 163, 184, 0.38)",
                        borderRadius: "999px",
                        padding: "8px 12px",
                        color: "#e2e8f0",
                        backdropFilter: "blur(3px)",
                        pointerEvents: "auto",
                    }}
                >
                    <label
                        title={
                            isGlobeProjection
                                ? "Dang o che do hinh cau (globe)"
                                : "Dang o che do trai phang (flat)"
                        }
                        style={{
                            display: "inline-flex",
                            alignItems: "center",
                            gap: "8px",
                            padding: "0 6px",
                            userSelect: "none",
                            cursor: "pointer",
                        }}
                    >
                        <input
                            type="checkbox"
                            checked={isGlobeProjection}
                            onChange={(e) => setIsGlobeProjection(e.target.checked)}
                            aria-label="Toggle globe projection"
                            style={{ display: "none" }}
                        />
                        <span
                            aria-hidden="true"
                            style={{
                                position: "relative",
                                width: "42px",
                                height: "22px",
                                borderRadius: "999px",
                                border: "1px solid rgba(148, 163, 184, 0.45)",
                                background: isGlobeProjection
                                    ? "rgba(56, 189, 248, 0.30)"
                                    : "rgba(148, 163, 184, 0.18)",
                                boxShadow: "inset 0 0 0 1px rgba(15, 23, 42, 0.35)",
                                transition: "background 160ms ease",
                            }}
                        >
                            <span
                                style={{
                                    position: "absolute",
                                    top: "2px",
                                    left: isGlobeProjection ? "22px" : "2px",
                                    width: "18px",
                                    height: "18px",
                                    borderRadius: "999px",
                                    background: isGlobeProjection ? "#38bdf8" : "#e2e8f0",
                                    boxShadow: "0 1px 3px rgba(0, 0, 0, 0.35)",
                                    transition: "left 160ms ease, background 160ms ease",
                                }}
                            />
                        </span>
                        <span
                            style={{
                                fontSize: "12px",
                                color: isGlobeProjection ? "#7dd3fc" : "#cbd5e1",
                                fontWeight: 700,
                                minWidth: "40px",
                            }}
                        >
                            {isGlobeProjection ? "Globe" : "Flat"}
                        </span>
                    </label>

                    <button
                        type="button"
                        onClick={() => handleZoomByStep(-0.8)}
                        style={zoomButtonStyle}
                        aria-label="Zoom out"
                    >
                        -
                    </button>

                    <input
                        type="range"
                        min={zoomBounds.min}
                        max={zoomBounds.max}
                        step={0.1}
                        value={zoomLevel}
                        onPointerDown={(event) => {
                            event.stopPropagation();
                            try {
                                event.currentTarget.setPointerCapture(event.pointerId);
                            } catch {
                                // Browser may reject capture for non-primary pointers; drag lock still works.
                            }
                            beginZoomSliderDrag();
                        }}
                        onPointerUp={(event) => {
                            event.stopPropagation();
                            try {
                                event.currentTarget.releasePointerCapture(event.pointerId);
                            } catch {
                                // Ignore if capture was already released.
                            }
                            endZoomSliderDrag();
                        }}
                        onPointerCancel={endZoomSliderDrag}
                        onBlur={endZoomSliderDrag}
                        onChange={(event) => handleZoomSliderChange(Number(event.target.value))}
                        style={{
                            flex: 1,
                            accentColor: "#38bdf8",
                            cursor: "pointer",
                        }}
                        aria-label="Map zoom"
                    />

                    <button
                        type="button"
                        onClick={() => handleZoomByStep(0.8)}
                        style={zoomButtonStyle}
                        aria-label="Zoom in"
                    >
                        +
                    </button>

                    <div
                        style={{
                            minWidth: "56px",
                            textAlign: "right",
                            fontSize: "12px",
                            color: "#cbd5e1",
                            fontVariantNumeric: "tabular-nums",
                        }}
                    >
                        {zoomLevel.toFixed(1)}x
                    </div>
                </div>
            </div>
        </div>
    );
});

export default Map;

const zoomButtonStyle: React.CSSProperties = {
    width: "28px",
    height: "28px",
    borderRadius: "999px",
    border: "1px solid #334155",
    background: "#1e293b",
    color: "#f8fafc",
    fontSize: "18px",
    lineHeight: "1",
    cursor: "pointer",
    display: "grid",
    placeItems: "center",
};
