﻿import React, { useEffect, useRef, useState } from 'react';
import { GeoJSON, MapContainer, TileLayer, useMap } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import './Travel.css';

const PHOTO_CHUNK_SIZE = 60;
const THUMB_STRIP_LIMIT = 36;

const normalizePhotos = (items = []) =>
    items
        .map((item) => {
            if (typeof item === 'string') return { src: item, title: '' };
            if (!item) return null;
            return {
                src: item.thumb || item.url || item.path || item.src || '',
                title: item.title || item.name || item.file || '',
                original: item.url || item.path || item.src || '',
                file: item.file || '',
                album: item.album || '',
            };
        })
        .filter((item) => item && item.src);

const getPhotoLabel = (photo) => {
    if (!photo) return '';
    if (photo.title) return photo.title;
    if (photo.file) return photo.file;
    if (!photo.src) return '';
    const parts = photo.src.split('/');
    return parts[parts.length - 1];
};

const getStripRange = (length, center) => {
    if (length <= THUMB_STRIP_LIMIT) return [0, length];
    const half = Math.floor(THUMB_STRIP_LIMIT / 2);
    let start = Math.max(0, center - half);
    let end = start + THUMB_STRIP_LIMIT;
    if (end > length) {
        end = length;
        start = end - THUMB_STRIP_LIMIT;
    }
    return [start, end];
};

const TravelPhotoGrid = ({ photos, regionLabel, onSelectPhoto }) => {
    const [visibleCount, setVisibleCount] = useState(() =>
        Math.min(PHOTO_CHUNK_SIZE, photos.length)
    );
    const sentinelRef = useRef(null);

    useEffect(() => {
        setVisibleCount(Math.min(PHOTO_CHUNK_SIZE, photos.length));
    }, [photos.length]);

    useEffect(() => {
        const sentinel = sentinelRef.current;
        if (!sentinel) return undefined;

        const observer = new IntersectionObserver(
            (entries) => {
                if (!entries[0]?.isIntersecting) return;
                setVisibleCount((prev) =>
                    Math.min(prev + PHOTO_CHUNK_SIZE, photos.length)
                );
            },
            { rootMargin: '240px' }
        );

        observer.observe(sentinel);
        return () => observer.disconnect();
    }, [photos.length]);

    const visiblePhotos = photos.slice(0, visibleCount);
    const canLoadMore = visibleCount < photos.length;

    return (
        <>
            <div className="travel-grid">
                {visiblePhotos.map((photo, index) => {
                    const label = getPhotoLabel(photo);
                    return (
                        <article
                            key={`${regionLabel}-${photo.src}`}
                            className={`travel-card ${
                                index % 6 === 0 ? 'is-wide' : ''
                            }`}
                            onClick={(event) => onSelectPhoto(index, event)}
                            role="button"
                            tabIndex={0}
                            onKeyDown={(event) => {
                                if (event.key === 'Enter') {
                                    onSelectPhoto(index, event);
                                }
                            }}
                        >
                            <img
                                src={photo.src}
                                alt={label}
                                loading="lazy"
                                onError={(event) => {
                                    const img = event.currentTarget;
                                    if (photo.original && img.src !== photo.original) {
                                        img.src = photo.original;
                                    }
                                }}
                            />
                            <div className="travel-card__overlay">
                                <p className="travel-card__title">{label}</p>
                            </div>
                        </article>
                    );
                })}
            </div>
            <div className="travel-album__footer" ref={sentinelRef}>
                {canLoadMore ? (
                    <button
                        type="button"
                        className="travel-load-more"
                        onClick={() =>
                            setVisibleCount((prev) =>
                                Math.min(prev + PHOTO_CHUNK_SIZE, photos.length)
                            )
                        }
                    >
                        Load more ({visibleCount}/{photos.length})
                    </button>
                ) : null}
            </div>
        </>
    );
};

const RegionsLayer = ({ geojson, onSelectRegion }) => {
    const map = useMap();

    if (!geojson) return null;

    return (
        <GeoJSON
            data={geojson}
            style={() => ({
                color: '#7c7c7c',
                weight: 1,
                fillOpacity: 0.2,
            })}
            onEachFeature={(feature, layer) => {
                layer.on('click', () => {
                    if (!feature?.properties?.id) return;
                    map.fitBounds(layer.getBounds(), {
                        padding: [40, 40],
                        animate: true,
                    });
                    onSelectRegion({
                        id: feature.properties.id,
                        name: feature.properties.name || feature.properties.id,
                    });
                });
            }}
        />
    );
};

const Travel = () => {
    const [photos, setPhotos] = useState([]);
    const [photoSummary, setPhotoSummary] = useState(null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState('');
    const [selectedRegion, setSelectedRegion] = useState(null);
    const [regionsGeojson, setRegionsGeojson] = useState(null);
    const [selectedPhotoIndex, setSelectedPhotoIndex] = useState(null);
    const [modalOffset, setModalOffset] = useState(24);
    const cacheRef = useRef(new Map());
    const cacheTtlMs = 10 * 60 * 1000;

    useEffect(() => {
        const controller = new AbortController();

        const loadRegions = async () => {
            try {
                const regionRes = await fetch('/api/travel/regions', {
                    signal: controller.signal,
                });
                if (!regionRes.ok) {
                    throw new Error(
                        `지역 정보 로딩 실패 (${regionRes.status})`
                    );
                }
                const regionJson = await regionRes.json();
                setRegionsGeojson(regionJson);
            } catch (err) {
                if (err?.name === 'AbortError') return;
                setError(err?.message ?? String(err));
            }
        };

        loadRegions();
        return () => controller.abort();
    }, []);

    useEffect(() => {
        if (!selectedRegion) return undefined;
        const controller = new AbortController();

        const loadRegionPhotos = async () => {
            const cached = cacheRef.current.get(selectedRegion.id);
            if (cached && Date.now() - cached.timestamp < cacheTtlMs) {
                setPhotos(cached.items);
                if (cached.items.length > 0) {
                    setModalOffset(24);
                    setSelectedPhotoIndex(0);
                } else {
                    setSelectedPhotoIndex(null);
                }
                return;
            }
            setLoading(true);
            setError('');

            try {
                const photoRes = await fetch(
                    `/api/travel/photos?region=${encodeURIComponent(
                        selectedRegion.id
                    )}`,
                    { signal: controller.signal }
                );
                if (!photoRes.ok) {
                    throw new Error(
                        `지역 사진 로딩 실패 (${photoRes.status})`
                    );
                }
                const photoJson = await photoRes.json();
                const items = Array.isArray(photoJson)
                    ? photoJson
                    : photoJson.items ?? [];
                const summarySource = Array.isArray(photoJson)
                    ? {}
                    : photoJson ?? {};
                setPhotoSummary({
                    total: summarySource.total ?? items.length,
                    albums: summarySource.matched_albums ?? [],
                });
                const normalized = normalizePhotos(items);
                cacheRef.current.set(selectedRegion.id, {
                    timestamp: Date.now(),
                    items: normalized,
                });
                setPhotos(normalized);
                if (normalized.length > 0) {
                    setModalOffset(24);
                    setSelectedPhotoIndex(0);
                } else {
                    setSelectedPhotoIndex(null);
                }
            } catch (err) {
                if (err?.name === 'AbortError') return;
                setError(err?.message ?? String(err));
                setPhotos([]);
                setPhotoSummary(null);
            } finally {
                setLoading(false);
            }
        };

        loadRegionPhotos();
        return () => controller.abort();
    }, [selectedRegion]);

    useEffect(() => {
        setSelectedPhotoIndex(null);
        setPhotoSummary(null);
    }, [selectedRegion]);

    useEffect(() => {
        if (selectedPhotoIndex === null) return undefined;

        const handleKeyDown = (event) => {
            if (event.key === 'Escape') {
                setSelectedPhotoIndex(null);
                return;
            }
            if (event.key === 'ArrowLeft') {
                setSelectedPhotoIndex((prev) =>
                    prev === null
                        ? prev
                        : (prev - 1 + photos.length) % photos.length
                );
            }
            if (event.key === 'ArrowRight') {
                setSelectedPhotoIndex((prev) =>
                    prev === null ? prev : (prev + 1) % photos.length
                );
            }
        };

        window.addEventListener('keydown', handleKeyDown);
        return () => window.removeEventListener('keydown', handleKeyDown);
    }, [photos.length, selectedPhotoIndex]);

    useEffect(() => {
        if (selectedPhotoIndex === null) return undefined;
        const { overflow } = document.body.style;
        document.body.style.overflow = 'hidden';
        return () => {
            document.body.style.overflow = overflow;
        };
    }, [selectedPhotoIndex]);

    const [stripStart, stripEnd] =
        selectedPhotoIndex === null
            ? [0, 0]
            : getStripRange(photos.length, selectedPhotoIndex);

    const handleSelectPhoto = (index, event) => {
        if (event) {
            const pointY =
                typeof event.clientY === 'number'
                    ? event.clientY
                    : event?.currentTarget?.getBoundingClientRect
                      ? event.currentTarget.getBoundingClientRect().top
                      : null;
            if (pointY !== null) {
                const nextOffset = Math.min(
                    Math.max(pointY - 120, 16),
                    window.innerHeight - 200
                );
                setModalOffset(nextOffset);
            } else {
                setModalOffset(24);
            }
        } else {
            setModalOffset(24);
        }
        setSelectedPhotoIndex(index);
    };

    return (
        <div className="travel">
            <header className="travel-header">
                <div>
                    <p className="travel-kicker">Visual Diary</p>
                    <h1>Travel Archive</h1>
                    <p className="travel-sub">
                        여행에서 느낀 감성과 분위기를 모아 전시하는 페이지입니다.
                    </p>
                </div>
                <div className="travel-note">
                    <p className="travel-note__title">폴더별 큐레이션</p>
                    <p className="travel-note__desc">
                        지역마다 그리드와 기록의 흐름을 다르게 배치해 리듬감을 만들었습니다.
                    </p>
                </div>
            </header>

            <section
                className={`travel-albums ${
                    selectedPhotoIndex !== null ? 'is-blurred' : ''
                }`}
            >
                <div className="travel-map">
                    <div className="travel-map__info">
                        <p className="travel-map__title">Select a region</p>
                        <p className="travel-map__desc">
                            {selectedRegion
                                ? `${selectedRegion.name} 사진을 불러옵니다.`
                                : '지도에서 지역을 클릭하면 해당 사진만 로딩됩니다.'}
                        </p>
                    </div>
                    <MapContainer
                        center={[20, 0]}
                        zoom={2}
                        scrollWheelZoom
                        className="travel-map__canvas"
                    >
                        <TileLayer
                            attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>'
                            url="https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png"
                        />
                        <RegionsLayer
                            geojson={regionsGeojson}
                            onSelectRegion={setSelectedRegion}
                        />
                    </MapContainer>
                </div>

                {loading ? (
                    <p className="travel-state">사진을 불러오는 중...</p>
                ) : null}
                {error ? <p className="travel-error">{error}</p> : null}
                {!loading && !error && selectedRegion && photos.length === 0 ? (
                    <p className="travel-state">
                        선택한 지역에 사진이 없습니다.
                    </p>
                ) : null}

                {null}
            </section>
            {selectedPhotoIndex !== null ? (
                <div
                    className="travel-modal"
                    role="dialog"
                    aria-modal="true"
                    onClick={() => setSelectedPhotoIndex(null)}
                    style={{ '--modal-offset': `${modalOffset}px` }}
                >
                    <div
                        className="travel-modal__content"
                        onClick={(event) => event.stopPropagation()}
                        style={{ marginTop: `${modalOffset}px` }}
                    >
                        <div className="travel-modal__summary">
                            <p className="travel-modal__summary-title">
                                {selectedRegion?.name || 'Region'} ·{' '}
                                {photoSummary?.total ?? photos.length} photos
                            </p>
                            {photoSummary?.albums?.length ? (
                                <p className="travel-modal__summary-meta">
                                    {photoSummary.albums
                                        .map((album) => album.album)
                                        .join(', ')}
                                </p>
                            ) : null}
                        </div>
                        <button
                            type="button"
                            className="travel-modal__close"
                            onClick={() => setSelectedPhotoIndex(null)}
                            aria-label="Close"
                        >
                            ×
                        </button>
                        <div className="travel-modal__nav">
                            <button
                                type="button"
                                className="travel-modal__arrow"
                                onClick={() =>
                                    setSelectedPhotoIndex((prev) =>
                                        prev === null
                                            ? prev
                                            : (prev - 1 + photos.length) %
                                              photos.length
                                    )
                                }
                                aria-label="Previous"
                            >
                                {'<'}
                            </button>
                            <button
                                type="button"
                                className="travel-modal__arrow"
                                onClick={() =>
                                    setSelectedPhotoIndex((prev) =>
                                        prev === null
                                            ? prev
                                            : (prev + 1) % photos.length
                                    )
                                }
                                aria-label="Next"
                            >
                                {'>'}
                            </button>
                        </div>
                        <div className="travel-modal__strip" role="list">
                            {photos
                                .slice(stripStart, stripEnd)
                                .map((photo, idx) => {
                                    const realIndex = stripStart + idx;
                                    return (
                                        <button
                                            key={`${photo.src}-${realIndex}`}
                                            type="button"
                                            className={`travel-modal__thumb ${
                                                realIndex === selectedPhotoIndex
                                                    ? 'is-active'
                                                    : ''
                                            }`}
                                            onClick={() =>
                                                setSelectedPhotoIndex(realIndex)
                                            }
                                            aria-label={getPhotoLabel(photo)}
                                            role="listitem"
                                        >
                                            <img
                                                src={photo.src}
                                                alt={getPhotoLabel(photo)}
                                                loading="lazy"
                                                onError={(event) => {
                                                    const img = event.currentTarget;
                                                    if (
                                                        photo.original &&
                                                        img.src !== photo.original
                                                    ) {
                                                        img.src = photo.original;
                                                    }
                                                }}
                                            />
                                        </button>
                                    );
                                })}
                        </div>
                        <img
                            src={
                                photos[selectedPhotoIndex]?.original ||
                                photos[selectedPhotoIndex]?.src
                            }
                            alt={getPhotoLabel(photos[selectedPhotoIndex])}
                        />
                        {photos[selectedPhotoIndex]?.album ||
                        photos[selectedPhotoIndex]?.file ? (
                            <p className="travel-modal__meta">
                                {photos[selectedPhotoIndex]?.album}{' '}
                                {photos[selectedPhotoIndex]?.file
                                    ? `- ${photos[selectedPhotoIndex]?.file}`
                                    : ''}
                            </p>
                        ) : null}
                    </div>
                </div>
            ) : null}
        </div>
    );
};

export default Travel;
