import {ArrowUpRight, MapPin, Utensils} from 'lucide-react';
import type {LocalPlace} from '@o2o/shared';
import {useState} from 'react';
import {useSite} from '@site/lib/site-context';
import {formatKoreanDate, naverMapSearchUrl} from '@site/lib/format';
import {Carousel, CarouselSlide, Section} from '@site/lib/ui';
/**
* 거리 탭.
*
* ★ 왜 도보 '분' 인가 (2026-09-04, 사장님 제안: "~km 이내 탭")
* 손님이 실제로 묻는 건 "걸어서 갈 수 있나"지 몇 km 인가가 아니다. 그래서 미터를
* **도보 시간(80m/분)** 으로 바꿔 라벨에 쓴다. 실측 데이터도 그렇게 갈린다 —
* 맛집 열둘이 전부 500m 안이고, 명소는 13m 부터 2.7km 까지 흩어져 있다.
* ★ 거른 것은 지우지 않고 접는다(hidden). 잘라내면 검색·AI 가 나머지를 못 읽는다.
* ★ 라벨에 '이내 / 이상' 을 붙인다 (2026-09-04, 사장님 지시)
* "걸어서 10분" 만 적으면 10분짜리만 모아 둔 칸으로 읽힌다. 경계값이 어느 쪽인지가
* 이 탭의 전부라, 포함 방향을 말로 박아 둔다. 마지막 칸은 나머지를 받는 '이상' 이다.
*/
const WALK = 80; // m/분. 도보 속도의 통상값이다.
const RANGES = [
{id: 'all', label: '전체', test: () => true},
{id: 'w5', label: '걸어서 5분 이내', test: (m: number) => m <= 5 * WALK},
{id: 'w10', label: '걸어서 10분 이내', test: (m: number) => m <= 10 * WALK},
{id: 'far', label: '걸어서 10분 이상', test: (m: number) => m > 10 * WALK},
] as const;
/**
* 숙소에서 걸리는 시간.
*
* ★ 카드마다 낸다 (2026-09-07, 사장님 지시)
* 거리 탭은 "걸어서 10분 이내"로 갈라 놓고 카드에는 "248m"만 찍혀 있었다. 손님은
* 248m 가 그 탭 안인지 밖인지 매번 암산해야 했다 — 탭이 이미 쓰는 환산을 카드도 쓴다.
* ★ 새 값을 payload 에 넣지 않고 여기서 환산한다. `distanceText` 는 수집이 준 사실이고
* 분은 그 사실에서 나온 것이라, 두 곳에 적으면 갈린다(거리 탭도 같은 상수를 본다).
* ★ 직선거리 기준이라 실제로 걷는 길보다 짧다. 그래서 **'약'** 을 붙이고 각주로 근거를 적는다.
*/
function walkText(m: number): string | undefined {
if (!Number.isFinite(m)) return undefined;
return `도보 약 ${Math.max(1, Math.round(m / WALK))}분`;
}
/** "135m" · "1.1km" → 미터. 못 읽으면 Infinity — 모르는 거리를 가깝다고 하지 않는다. */
function meters(text: string | undefined): number {
const m = /^([\d.]+)\s*(km|m)$/i.exec((text ?? '').trim());
if (!m) return Infinity;
return Number(m[1]) * (m[2].toLowerCase() === 'km' ? 1000 : 1);
}
function distanceOf(place: LocalPlace): number {
// 숫자가 없는 옛 payload만 표기 문자열로 복원한다.
if (place.distanceMeters != null) {
return Number.isFinite(place.distanceMeters) && place.distanceMeters >= 0
? place.distanceMeters
: Infinity;
}
return meters(place.distanceText);
}
/**
* 주변 정보.
*
* "○○ 근처 숙소" 같은 지역 질의에 걸리려면 지역 고유명사가 본문에 있어야 한다.
* ★ 우리가 남의 가게 URL 을 지어내지 않는다 — 검색 링크로만 보낸다.
* 틀린 링크는 사용자를 엉뚱한 데로 보내고, 그 책임은 이 홈페이지가 진다.
*
* ★ 880px 한 단에 세로로 쌓던 자리다. 맛집 5 · 명소 5 면 화면 두 개 분량의
* 한 줄짜리 띠가 이어졌다. 카드 그리드로 벌리면 같은 내용이 한 화면에 들어온다.
*/
export function LocalGuideSection() {
const payload = useSite();
const {local} = payload;
// ★ 축제·행사는 여기서 그리지 않는다 (2026-09-03) — 날짜가 지나면 못 가는 것이라
// 아무 때나 가는 맛집·명소와 성격이 다르다. 계절을 축으로 세운 FestivalSection 이 맡는다.
// ★ 맛집은 사진이 없으면 아예 목록에서 뺀다 (2026-09-15, 사장님 지시) —
// 명소는 이름 활자로 대신하는 규칙을 유지하지만, 맛집은 사진 없는 곳을 보여주지 않는다.
const restaurants = local.restaurants.filter((place) => place.imageUrl);
const hasAnything = restaurants.length > 0 || local.attractions.length > 0;
const [range, setRange] = useState
{place.description}
} {place.location &&{place.location}
}이 거리 안에는 없습니다.
)}