import { useCallback, useEffect, useMemo, useRef, useState, type FormEvent } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { useAuthStore } from '../../store/authStore';
import { useResolvedEventId } from '../../hooks/useResolvedEventId';
import { exhibitionApi, loginSlideApi, noticeApi, notificationApi } from '../../api/endpoints';
import type { ExhibitionDto } from '../../api/types';
import { AiLabel, DdayChip } from '../../components/ui/Badge';
import { Button } from '../../components/ui/Button';
import { EmptyState, ErrorState, Skeleton } from '../../components/ui/States';
import {
  IconAiBot,
  IconBell,
  IconBolt,
  IconCalendar,
  IconCheckCircle,
  IconChevronLeft,
  IconChevronRight,
  IconClock,
  IconDocument,
  IconExhibitors,
  IconFloorplan,
  IconGrid,
  IconSearch,
  IconSettlement,
  IconSpark,
} from '../../components/ui/icons';
import {
  homeApi,
  type HomeTodoItem,
  type KpiTile,
  type ShowcaseItem,
  type ShowcaseTab,
} from './homeApi';
import {
  buildMonthGrid,
  eventsOnDay,
  type CalEvent,
  type CalStatus,
} from './homeCalendar';
import { fetchHolidaySet } from '../admin/holidayApi';
import { HomeCharts } from './HomeCharts';
import './home.css';

/** 오늘(로컬) — 캘린더 강조·상태 파생 기준. 서버 D-day 는 각 API 가 별도 계산. */
const TODAY = new Date();
const TODAY_YMD = ymd(TODAY);

function ymd(d: Date): string {
  const mm = String(d.getMonth() + 1).padStart(2, '0');
  const dd = String(d.getDate()).padStart(2, '0');
  return `${d.getFullYear()}-${mm}-${dd}`;
}

/** 날짜 기준 전시 상태(진행/예정/종료). DB status 원문 대신 오늘 기준 재계산(일정 페이지와 동일 규약). */
function statusFromDates(start: string, end: string): CalStatus {
  if (end < TODAY_YMD) return 'ended';
  if (start > TODAY_YMD) return 'upcoming';
  return 'ongoing';
}

/** 홀 라벨이 선택 전시장(센터) 소속인지 — "제{code}전시장" 부분일치. code='all' 이면 전체 통과. */
function matchesCenter(hallLabel: string | null | undefined, code: string): boolean {
  if (code === 'all') return true;
  if (!hallLabel) return false;
  return new RegExp(`제\\s*${code}\\s*전시장`).test(hallLabel);
}

export function HomePage() {
  const { t } = useTranslation();
  const user = useAuthStore((s) => s.user);
  const name = user?.displayName ?? '';
  const dateLabel = useMemo(
    () =>
      TODAY.toLocaleDateString('ko-KR', {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        weekday: 'long',
      }),
    [],
  );

  return (
    <div className="kx-home" aria-label={t('home.pageAria')}>
      {/* ① 인사 + 역할별 KPI (단일 행) */}
      <section className="kx-home__hero">
        <KpiStrip name={name} dateLabel={dateLabel} />
      </section>

      {/* ①-b 요약 차트 (KPI 아래, Recharts) */}
      <HomeCharts />

      {/* ①-c KINTEX AI 어시스턴트 허브 (KPI 아래·배너 위 — AI 기능 홍보 스트립) */}
      <AiHubStrip />

      {/* ② 배너 캐러셀 */}
      <BannerCarousel />

      {/* ③ 쇼케이스 포스터 레일 */}
      <ShowcaseRail />

      {/* ④ 본문 2열 그리드 */}
      <div className="kx-home__body">
        <div className="kx-home__col-main">
          <HomeCalendar />
        </div>
        <div className="kx-home__col-side">
          <TodoWidget />
          <NoticeWidget />
          <QuickActions />
          <ActivityWidget />
        </div>
      </div>

      {/* ⑤ 내 행사 워크스페이스 */}
      <WorkspaceGrid />
    </div>
  );
}

/* ────────────────────────────────────────────────────────────
 * ① 인사 + KPI 스트립
 * ──────────────────────────────────────────────────────────── */
function roleLabel(role: string, t: (k: string) => string): string {
  switch (role) {
    case 'ORGANIZER':
      return t('home.role.organizer');
    case 'EXHIBITOR':
      return t('home.role.exhibitor');
    case 'CONTRACTOR':
      return t('home.role.contractor');
    case 'HALL_MANAGER':
      return t('home.role.hallManager');
    case 'ADMIN':
    case 'MANAGER':
      return t('home.role.admin');
    default:
      return role;
  }
}

function kpiValueText(tile: KpiTile): string {
  if (tile.value == null) return '—';
  const v = typeof tile.value === 'number' ? tile.value.toLocaleString('ko-KR') : tile.value;
  return tile.unit ? `${v}${tile.unit}` : v;
}

function KpiStrip({ name, dateLabel }: { name: string; dateLabel: string }) {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const query = useQuery({ queryKey: ['home-summary'], queryFn: () => homeApi.summary() });

  const role = query.data?.role ?? '';
  const tiles = query.data?.tiles ?? [];

  return (
    <div className="kx-home__hero-row">
      <div className="kx-home__greeting">
        <h1 className="kx-home__hello">
          {name ? t('home.greeting', { name }) : t('home.greetingGuest')}
        </h1>
        <p className="kx-home__submeta">
          <span className="tnum">{dateLabel}</span>
          {role && <span className="kx-badge kx-badge--role">{roleLabel(role, t)}</span>}
        </p>
      </div>

      <div className="kx-home__kpis" aria-label={t('home.kpi.aria')}>
        {query.isLoading ? (
          Array.from({ length: 4 }).map((_, i) => (
            <div key={i} className="kx-kpitile" aria-hidden="true">
              <Skeleton height={12} width="60%" />
              <Skeleton height={24} width="40%" />
            </div>
          ))
        ) : query.isError ? (
          <div className="kx-kpitile kx-kpitile--error">
            <ErrorState message={t('home.loadError')} onRetry={() => query.refetch()} />
          </div>
        ) : (
          tiles.map((tile) => (
            <button
              key={tile.key}
              type="button"
              className={`kx-kpitile is-${tile.severity}`}
              onClick={() => tile.route && navigate(tile.route)}
            >
              <span className="kx-kpitile__label">{tile.label}</span>
              <span className="kx-kpitile__value tnum">{kpiValueText(tile)}</span>
            </button>
          ))
        )}
      </div>
    </div>
  );
}

/* ────────────────────────────────────────────────────────────
 * ①-c KINTEX AI 어시스턴트 허브 (AI 기능 홍보 스트립)
 *   - 5개 AI 도구 카드(실 라우트) + 자연어 질문 퀵 입력(?q= 전달)
 *   - 행사 스코프가 필요한 카드(배치·갤러리)는 첫 워크스페이스 eventId 계산,
 *     없으면 /schedule 로 안내(QuickActions editor 패턴 정합).
 * ──────────────────────────────────────────────────────────── */
const AI_DEFAULT_HALL = 'H7'; // LayoutComparisonPage 기본 홀과 정합
const AI_DEFAULT_BOOTH = 'A-102'; // ExhibitorHome/Gallery 기본 부스와 정합(갤러리는 샘플 폴백)

interface AiTool {
  key: 'layout' | 'gallery' | 'nlquery' | 'rules' | 'leads';
  Icon: typeof IconSpark;
  go: () => void;
}

function AiHubStrip() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const eventId = useResolvedEventId();
  const [question, setQuestion] = useState('');

  const tools: AiTool[] = [
    {
      key: 'layout',
      Icon: IconSpark,
      go: () =>
        navigate(eventId ? `/events/${eventId}/halls/${AI_DEFAULT_HALL}/comparison` : '/schedule'),
    },
    {
      key: 'gallery',
      Icon: IconSpark,
      go: () =>
        navigate(eventId ? `/events/${eventId}/booths/${AI_DEFAULT_BOOTH}/gallery` : '/schedule'),
    },
    { key: 'nlquery', Icon: IconAiBot, go: () => navigate('/analytics?tab=nlquery') },
    { key: 'rules', Icon: IconAiBot, go: () => navigate('/analytics?tab=rules') },
    { key: 'leads', Icon: IconSpark, go: () => navigate('/leads') },
  ];

  function askSubmit(e: FormEvent) {
    e.preventDefault();
    const q = question.trim();
    navigate(q ? `/analytics?q=${encodeURIComponent(q)}` : '/analytics');
  }

  return (
    <section className="kx-home__aihub" aria-label={t('home.ai.aria')}>
      <div className="kx-home__aihub-head">
        <span className="kx-home__aihub-badgeicon" aria-hidden="true">
          <IconAiBot size={22} />
        </span>
        <div className="kx-home__aihub-heading">
          <h2 className="kx-home__aihub-title">
            {t('home.ai.title')}
            <AiLabel>{t('home.ai.badge')}</AiLabel>
          </h2>
          <p className="kx-home__aihub-sub">{t('home.ai.subtitle')}</p>
        </div>
        <form className="kx-home__aihub-ask" onSubmit={askSubmit} role="search">
          <input
            className="kx-home__aihub-input"
            value={question}
            onChange={(e) => setQuestion(e.target.value)}
            placeholder={t('home.ai.askPh')}
            aria-label={t('home.ai.askAria')}
          />
          <Button variant="ai" type="submit">
            {t('home.ai.askBtn')}
          </Button>
        </form>
      </div>

      <div className="kx-home__aihub-grid">
        {tools.map((tool) => (
          <button
            key={tool.key}
            type="button"
            className="kx-home__aihub-card"
            onClick={tool.go}
          >
            <span className="kx-home__aihub-card-icon" aria-hidden="true">
              <tool.Icon size={18} />
            </span>
            <span className="kx-home__aihub-card-main">
              <span className="kx-home__aihub-card-title">
                {t(`home.ai.tool.${tool.key}.title`)}
                <span className="kx-home__aihub-card-tag" aria-hidden="true">
                  AI
                </span>
              </span>
              <span className="kx-home__aihub-card-desc">
                {t(`home.ai.tool.${tool.key}.desc`)}
              </span>
            </span>
          </button>
        ))}
      </div>
    </section>
  );
}

/* ────────────────────────────────────────────────────────────
 * ② 배너 캐러셀 (login-slides 재사용, 220~260px)
 * ──────────────────────────────────────────────────────────── */
interface BannerSlide {
  id: number;
  imageUrl: string;
  title: string;
  caption: string | null;
}
const BANNER_INTERVAL = 5000;

function BannerCarousel() {
  const { t } = useTranslation();
  const [slides, setSlides] = useState<BannerSlide[] | null>(null);
  const [index, setIndex] = useState(0);
  const [paused, setPaused] = useState(false);
  const timerRef = useRef<number | null>(null);

  useEffect(() => {
    const ctrl = new AbortController();
    loginSlideApi
      .publicList()
      .then((list) => {
        if (ctrl.signal.aborted) return;
        setSlides(
          (list ?? []).map((s) => ({
            id: s.id,
            imageUrl: s.imageUrl,
            title: s.title,
            caption: s.caption,
          })),
        );
      })
      .catch(() => setSlides([]));
    return () => ctrl.abort();
  }, []);

  const reduceMotion =
    typeof window !== 'undefined' &&
    window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;

  const count = slides?.length ?? 0;
  useEffect(() => {
    if (count < 2 || paused || reduceMotion) return;
    timerRef.current = window.setInterval(
      () => setIndex((i) => (i + 1) % count),
      BANNER_INTERVAL,
    );
    return () => {
      if (timerRef.current != null) window.clearInterval(timerRef.current);
    };
  }, [count, paused, reduceMotion]);

  if (slides == null) {
    return (
      <section className="kx-home__banner" aria-busy="true">
        <Skeleton height={240} radius={8} />
      </section>
    );
  }

  if (slides.length === 0) {
    // 배너 미등록 — 밀도 원칙 ④: 브랜드 그라디언트 폴백 + CTA
    return (
      <section className="kx-home__banner">
        <div className="kx-home__banner-fallback">
          <div className="kx-home__banner-text">
            <h2>{t('home.banner.emptyTitle')}</h2>
            <p>{t('home.banner.emptyDesc')}</p>
          </div>
        </div>
      </section>
    );
  }

  const cur = slides[index] ?? slides[0];
  const go = (n: number) => setIndex((n + slides.length) % slides.length);

  return (
    <section
      className="kx-home__banner"
      aria-roledescription="carousel"
      aria-label={t('home.banner.aria')}
      onMouseEnter={() => setPaused(true)}
      onMouseLeave={() => setPaused(false)}
      onFocus={() => setPaused(true)}
      onBlur={() => setPaused(false)}
    >
      {slides.map((s, i) => (
        <div
          key={s.id}
          className={`kx-home__banner-slide ${i === index ? 'is-active' : ''}`}
          style={{ backgroundImage: `url(${s.imageUrl})` }}
          aria-hidden={i !== index}
        />
      ))}
      <div className="kx-home__banner-scrim" />
      <div className="kx-home__banner-body">
        <h2 className="kx-home__banner-title" key={`t-${cur.id}`}>
          {cur.title}
        </h2>
        {cur.caption && (
          <p className="kx-home__banner-sub" key={`c-${cur.id}`}>
            {cur.caption}
          </p>
        )}
      </div>

      {slides.length > 1 && (
        <>
          <button
            type="button"
            className="kx-home__banner-nav is-prev"
            aria-label={t('home.banner.prev')}
            onClick={() => go(index - 1)}
          >
            <IconChevronLeft size={20} />
          </button>
          <button
            type="button"
            className="kx-home__banner-nav is-next"
            aria-label={t('home.banner.next')}
            onClick={() => go(index + 1)}
          >
            <IconChevronRight size={20} />
          </button>
          <div className="kx-home__banner-dots" role="tablist" aria-label={t('home.banner.aria')}>
            {slides.map((s, i) => (
              <button
                key={s.id}
                type="button"
                role="tab"
                aria-selected={i === index}
                aria-label={`${i + 1}`}
                className={`kx-home__banner-dot ${i === index ? 'is-active' : ''}`}
                onClick={() => go(i)}
              />
            ))}
          </div>
        </>
      )}
    </section>
  );
}

/* ────────────────────────────────────────────────────────────
 * ③ 쇼케이스 포스터 레일
 * ──────────────────────────────────────────────────────────── */
// 탭 순서: 이번 달 → 다음 달 → 진행 중(맨 끝, 소유자 지시 2026-07-12). 기본=이번 달.
const SHOWCASE_TABS: ShowcaseTab[] = ['thisMonth', 'nextMonth', 'ongoing'];

function ShowcaseRail() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const [tab, setTab] = useState<ShowcaseTab>('thisMonth');
  const railRef = useRef<HTMLDivElement | null>(null);
  const [atStart, setAtStart] = useState(true);
  const [atEnd, setAtEnd] = useState(false);

  const query = useQuery({
    queryKey: ['home-showcase', tab],
    queryFn: () => homeApi.showcase(tab),
  });
  const items = query.data ?? [];

  const updateArrows = useCallback(() => {
    const el = railRef.current;
    if (!el) return;
    setAtStart(el.scrollLeft <= 4);
    setAtEnd(el.scrollLeft + el.clientWidth >= el.scrollWidth - 4);
  }, []);

  useEffect(() => {
    updateArrows();
  }, [items, updateArrows]);

  const scroll = (dir: number) => {
    const el = railRef.current;
    if (!el) return;
    el.scrollBy({ left: dir * Math.max(320, el.clientWidth * 0.8), behavior: 'smooth' });
  };

  return (
    <section className="kx-home__showcase" aria-label={t('home.showcase.title')}>
      <div className="kx-home__section-head">
        <h2 className="kx-home__section-title">
          <IconGrid className="kx-title-ic" size={18} />
          {t('home.showcase.title')}
        </h2>
        <div className="kx-seg" role="tablist" aria-label={t('home.showcase.title')}>
          {SHOWCASE_TABS.map((k) => (
            <button
              key={k}
              type="button"
              role="tab"
              aria-selected={tab === k}
              className={`kx-seg__btn ${tab === k ? 'is-active' : ''}`}
              onClick={() => setTab(k)}
            >
              {t(`home.showcase.tab.${k}`)}
            </button>
          ))}
        </div>
        <button
          type="button"
          className="kx-home__section-link"
          onClick={() => navigate('/schedule')}
        >
          {t('home.showcase.all')}
        </button>
      </div>

      {query.isLoading ? (
        <div className="kx-home__rail" aria-busy="true">
          {Array.from({ length: 6 }).map((_, i) => (
            <div key={i} className="kx-poster" aria-hidden="true">
              <Skeleton height={200} radius={8} />
              <Skeleton height={14} width="80%" />
              <Skeleton height={12} width="55%" />
            </div>
          ))}
        </div>
      ) : query.isError ? (
        <div className="kx-card">
          <ErrorState message={t('home.loadError')} onRetry={() => query.refetch()} />
        </div>
      ) : items.length === 0 ? (
        <div className="kx-card">
          <EmptyState
            title={t('home.showcase.emptyTitle')}
            action={<Button variant="secondary" onClick={() => navigate('/schedule')}>{t('home.showcase.emptyCta')}</Button>}
          />
        </div>
      ) : (
        <div className="kx-home__rail-wrap">
          {!atStart && (
            <button
              type="button"
              className="kx-home__rail-arrow is-prev"
              aria-label={t('home.showcase.prev')}
              onClick={() => scroll(-1)}
            >
              <IconChevronLeft size={20} />
            </button>
          )}
          <div className="kx-home__rail" role="list" ref={railRef} onScroll={updateArrows}>
            {items.map((ev) => (
              <PosterCard key={ev.id} ev={ev} />
            ))}
          </div>
          {!atEnd && (
            <button
              type="button"
              className="kx-home__rail-arrow is-next"
              aria-label={t('home.showcase.next')}
              onClick={() => scroll(1)}
            >
              <IconChevronRight size={20} />
            </button>
          )}
        </div>
      )}
    </section>
  );
}

/** "2026-08-13"·"2026-08-16" → "08.13–08.16" (동월) / "2026.08.13–2026.09.02" */
function periodShort(start: string, end: string): string {
  const s = start.replace(/-/g, '.').slice(5);
  const sameYear = start.slice(0, 4) === end.slice(0, 4);
  const sameMonth = sameYear && start.slice(5, 7) === end.slice(5, 7);
  const e = sameMonth ? end.slice(8, 10) : end.replace(/-/g, '.').slice(5);
  return `${s}–${e}`;
}

function PosterCard({ ev }: { ev: ShowcaseItem }) {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const statusLabel =
    ev.status === 'ongoing' ? t('home.showcase.statusOngoing') : t('home.showcase.statusUpcoming');
  return (
    <button
      type="button"
      role="listitem"
      className="kx-poster"
      onClick={() => navigate(`/public/events/${encodeURIComponent(ev.id)}`)}
    >
      {ev.posterUrl ? (
        <span
          className="kx-poster__img"
          style={{ backgroundImage: `url(${ev.posterUrl})` }}
          aria-hidden="true"
        />
      ) : (
        <span className="kx-poster__img kx-poster__img--fallback" aria-hidden="true">
          <span className="kx-poster__fallback-text">{ev.name}</span>
        </span>
      )}
      {/* Stitch 정합: 홀 라벨(상단·상태색) → 행사명 → 기간 → 하단 배지행, 좌측 상태 액센트 */}
      <span className={`kx-poster__body is-${ev.status}`}>
        {ev.hallLabel && <span className="kx-poster__hall">{ev.hallLabel}</span>}
        <span className="kx-poster__name">{ev.name}</span>
        <span className="kx-poster__meta tnum">{periodShort(ev.startDate, ev.endDate)}</span>
        <span className="kx-poster__badges">
          <span className={`kx-home__chip is-${ev.status}`}>{statusLabel}</span>
          {ev.status !== 'ongoing' && <DdayChip dday={ev.dday} />}
        </span>
      </span>
    </button>
  );
}

/* ────────────────────────────────────────────────────────────
 * ④-좌 월 캘린더 (전시장 세그먼트 필터 + spanning bar) + 진행 리스트
 * ──────────────────────────────────────────────────────────── */
function catStatusClass(s: CalStatus): string {
  return s;
}

function HomeCalendar() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const [cursor, setCursor] = useState(new Date(TODAY.getFullYear(), TODAY.getMonth(), 1));
  const [center, setCenter] = useState('all');
  const [popupDay, setPopupDay] = useState<string | null>(null);

  const eventsQuery = useQuery({
    queryKey: ['home-cal-events'],
    queryFn: async (): Promise<CalEvent[]> => {
      const rows = await exhibitionApi.list();
      return rows.map((e: ExhibitionDto) => ({
        id: e.id,
        name: e.name,
        start: e.startDate,
        end: e.endDate,
        hallLabel: e.hallLabel ?? null,
        status: statusFromDates(e.startDate, e.endDate),
      }));
    },
  });
  const centersQuery = useQuery({
    queryKey: ['home-hall-centers'],
    queryFn: () => homeApi.hallCenters(),
  });
  // 공휴일 세트(붉은 날짜 + 공휴일명) — 커서 연도±1 창. 실패해도 달력 정상(빈 세트).
  const holidayQ = useQuery({
    queryKey: ['home-cal-holidays', cursor.getFullYear()],
    queryFn: () =>
      fetchHolidaySet(`${cursor.getFullYear() - 1}-01-01`, `${cursor.getFullYear() + 1}-12-31`),
    retry: false,
  });
  const holidays = holidayQ.data ?? new Map<string, string>();

  const allEvents = eventsQuery.data ?? [];
  const filtered = useMemo(
    () => allEvents.filter((e) => matchesCenter(e.hallLabel, center)),
    [allEvents, center],
  );

  const year = cursor.getFullYear();
  const month = cursor.getMonth();
  const weeks = useMemo(
    () => buildMonthGrid(year, month, filtered, TODAY),
    [year, month, filtered],
  );

  const monthTitle = t('home.calendar.yearMonth', { y: year, m: month + 1 });
  const popupEvents = popupDay ? eventsOnDay(filtered, popupDay) : [];

  // 진행 중/예정 리스트 — 오늘 이후 종료, 시작일 오름차순, 최대 8건
  const listed = useMemo(
    () =>
      [...filtered]
        .filter((e) => e.status !== 'ended')
        .sort((a, b) => (a.start < b.start ? -1 : 1))
        .slice(0, 8),
    [filtered],
  );

  const centerOptions = [
    { code: 'all', label: t('home.calendar.all') },
    ...(centersQuery.data ?? []),
  ];

  return (
    <section className="kx-card kx-home__cal" aria-label={t('home.calendar.title')}>
      <div className="kx-home__cal-head">
        <div className="kx-home__cal-toolbar">
          <button
            type="button"
            className="kx-home__cal-nav"
            aria-label={t('home.calendar.prev')}
            onClick={() => setCursor(new Date(year, month - 1, 1))}
          >
            <IconChevronLeft size={16} />
          </button>
          <strong className="kx-home__cal-title tnum">
            <IconCalendar className="kx-title-ic" size={16} />
            {monthTitle}
          </strong>
          <button
            type="button"
            className="kx-home__cal-nav"
            aria-label={t('home.calendar.next')}
            onClick={() => setCursor(new Date(year, month + 1, 1))}
          >
            <IconChevronRight size={16} />
          </button>
          <button
            type="button"
            className="kx-home__cal-today"
            onClick={() => setCursor(new Date(TODAY.getFullYear(), TODAY.getMonth(), 1))}
          >
            {t('home.calendar.today')}
          </button>
        </div>

        {/* 전시장 세그먼트(동적 옵션) */}
        <div className="kx-seg kx-home__cal-seg" role="tablist" aria-label={t('home.calendar.centerFilter')}>
          {centerOptions.map((c) => (
            <button
              key={c.code}
              type="button"
              role="tab"
              aria-selected={center === c.code}
              className={`kx-seg__btn ${center === c.code ? 'is-active' : ''}`}
              onClick={() => setCenter(c.code)}
            >
              {c.label}
            </button>
          ))}
        </div>
      </div>

      {/* 범례 */}
      <div className="kx-home__cal-legend" aria-hidden="true">
        <span className="kx-home__legend-item"><i className="kx-home__legend-dot is-ongoing" />{t('home.calendar.legendOngoing')}</span>
        <span className="kx-home__legend-item"><i className="kx-home__legend-dot is-upcoming" />{t('home.calendar.legendUpcoming')}</span>
        <span className="kx-home__legend-item"><i className="kx-home__legend-dot is-ended" />{t('home.calendar.legendEnded')}</span>
      </div>

      {eventsQuery.isLoading ? (
        <Skeleton height={360} radius={8} />
      ) : eventsQuery.isError ? (
        <ErrorState message={t('home.calendar.loadError')} onRetry={() => eventsQuery.refetch()} />
      ) : (
        <>
          <div className="kx-home__cal-grid">
            <div className="kx-home__cal-dow">
              {[0, 1, 2, 3, 4, 5, 6].map((i) => (
                <span
                  key={i}
                  className={`kx-home__cal-wd${i === 0 ? ' is-sun' : i === 6 ? ' is-sat' : ''}`}
                >
                  {t(`common.weekday.${i}`)}
                </span>
              ))}
            </div>
            {weeks.map((wk, wi) => (
              <div className="kx-home__cal-week" key={wi}>
                {/* 배경 일자 셀 */}
                <div className="kx-home__cal-cells">
                  {wk.cells.map((cell) => (
                    <button
                      type="button"
                      key={cell.key}
                      className={[
                        'kx-home__cal-cell',
                        cell.inMonth ? '' : 'is-out',
                        cell.isToday ? 'is-today' : '',
                        cell.dow === 0 ? 'is-sun' : cell.dow === 6 ? 'is-sat' : '',
                        holidays.get(cell.key) ? 'is-holiday' : '',
                      ]
                        .filter(Boolean)
                        .join(' ')}
                      onClick={() => setPopupDay(cell.key)}
                      title={holidays.get(cell.key) || undefined}
                    >
                      <span className="kx-home__cal-daynum tnum">{cell.day}</span>
                      {holidays.get(cell.key) && (
                        <span className="kx-home__cal-holiday-name">{holidays.get(cell.key)}</span>
                      )}
                      {wk.hiddenByCol[cell.dow] > 0 && (
                        <span className="kx-home__cal-more tnum">
                          {t('home.calendar.more', { n: wk.hiddenByCol[cell.dow] })}
                        </span>
                      )}
                    </button>
                  ))}
                </div>
                {/* spanning bar 레인 */}
                <div
                  className="kx-home__cal-lanes"
                  style={{ height: `${wk.laneCount * 22}px` }}
                  aria-hidden="true"
                >
                  {wk.bars.map((bar, bi) => (
                    <span
                      key={`${bar.event.id}-${bi}`}
                      className={[
                        'kx-home__cal-bar',
                        `is-${catStatusClass(bar.event.status)}`,
                        bar.continuesLeft ? 'is-cont-l' : '',
                        bar.continuesRight ? 'is-cont-r' : '',
                      ]
                        .filter(Boolean)
                        .join(' ')}
                      style={{
                        left: `calc(${(bar.colStart / 7) * 100}% + 2px)`,
                        width: `calc(${(bar.span / 7) * 100}% - 4px)`,
                        top: `${bar.lane * 22}px`,
                      }}
                      title={`${bar.event.name}${bar.event.hallLabel ? ` · ${bar.event.hallLabel}` : ''}`}
                    >
                      {bar.event.name}
                    </span>
                  ))}
                </div>
              </div>
            ))}
          </div>

          {/* 진행 중·예정 전시 리스트 */}
          <div className="kx-home__cal-list">
            <h3 className="kx-home__cal-list-title">
              <IconGrid className="kx-title-ic" size={16} />
              {t('home.calendar.listTitle')}
            </h3>
            {listed.length === 0 ? (
              <EmptyState
                title={t('home.calendar.listEmpty')}
                action={<Button variant="secondary" onClick={() => navigate('/schedule')}>{t('home.calendar.listEmptyCta')}</Button>}
              />
            ) : (
              <ul className="kx-home__cal-list-items">
                {listed.map((e) => (
                  <li key={e.id}>
                    <button
                      type="button"
                      className="kx-home__cal-list-row"
                      onClick={() => navigate(`/public/events/${encodeURIComponent(e.id)}`)}
                    >
                      <span className={`kx-home__legend-dot is-${e.status}`} aria-hidden="true" />
                      <span className="kx-home__cal-list-name">{e.name}</span>
                      {e.hallLabel && <span className="kx-home__cal-list-hall">{e.hallLabel}</span>}
                      <span className="kx-home__cal-list-period tnum">
                        {periodShort(e.start, e.end)}
                      </span>
                    </button>
                  </li>
                ))}
              </ul>
            )}
          </div>
        </>
      )}

      {/* 일자 팝업(+N / 셀 클릭) */}
      {popupDay && (
        <div
          className="kx-home__cal-popup-backdrop"
          role="presentation"
          onClick={() => setPopupDay(null)}
        >
          <div
            className="kx-home__cal-popup"
            role="dialog"
            aria-label={t('home.calendar.moreTitle', { date: popupDay })}
            onClick={(e) => e.stopPropagation()}
          >
            <div className="kx-home__cal-popup-head">
              <strong className="tnum">{popupDay}</strong>
              <button type="button" className="kx-home__cal-popup-close" onClick={() => setPopupDay(null)} aria-label={t('common.actions.close')}>
                ✕
              </button>
            </div>
            {popupEvents.length === 0 ? (
              <p className="kx-home__cal-popup-empty">{t('home.calendar.popupEmpty')}</p>
            ) : (
              <ul className="kx-home__cal-popup-list">
                {popupEvents.map((e) => (
                  <li key={e.id}>
                    <button
                      type="button"
                      onClick={() => {
                        setPopupDay(null);
                        navigate(`/public/events/${encodeURIComponent(e.id)}`);
                      }}
                    >
                      <span className={`kx-home__legend-dot is-${e.status}`} aria-hidden="true" />
                      <span>{e.name}</span>
                    </button>
                  </li>
                ))}
              </ul>
            )}
          </div>
        </div>
      )}
    </section>
  );
}

/* ────────────────────────────────────────────────────────────
 * ④-우① 내 할 일
 * ──────────────────────────────────────────────────────────── */
function todoSeverityDot(sev: HomeTodoItem['severity']): string {
  return sev === 'overdue' ? 'is-ended' : sev === 'warning' ? 'is-upcoming' : 'is-ongoing';
}
function todoDueText(item: HomeTodoItem, t: (k: string, o?: Record<string, unknown>) => string): string {
  // 기한 없는 핫리드 팔로업은 '팔로업' 라벨로 부각(다른 무기한 항목은 '기한 없음').
  if (item.dday == null) {
    return item.type === 'lead'
      ? t('home.todo.followup', { defaultValue: '팔로업' })
      : t('home.todo.noDue');
  }
  if (item.dday < 0) return `D+${Math.abs(item.dday)}`;
  return `D-${item.dday}`;
}
/** 할 일 유형 칩 라벨(로컬라이즈; i18n 병합 전에는 한국어 폴백). */
const TODO_TYPE_FALLBACK: Record<string, string> = {
  approval: '결재',
  document: '서류',
  milestone: '마일스톤',
  auction: '옥션',
  settlement: '정산',
  checkin: '체크인',
  lead: '리드',
  system: '시스템',
};
function todoTypeLabel(type: string, t: (k: string, o?: Record<string, unknown>) => string): string {
  return t(`home.todo.type.${type}`, { defaultValue: TODO_TYPE_FALLBACK[type] ?? type });
}

function TodoWidget() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const query = useQuery({ queryKey: ['home-todos'], queryFn: () => homeApi.todos() });
  const data = query.data;

  return (
    <section className="kx-card kx-home__widget" aria-label={t('home.todo.title')}>
      <div className="kx-home__widget-head">
        <h2>
          <IconCheckCircle className="kx-title-ic" size={18} />
          {t('home.todo.title')}
        </h2>
        {data && data.total > 0 && <span className="kx-home__count-badge tnum">{data.total}</span>}
      </div>

      {query.isLoading ? (
        <div className="kx-home__widget-body">
          {Array.from({ length: 4 }).map((_, i) => (
            <Skeleton key={i} height={40} />
          ))}
        </div>
      ) : query.isError ? (
        <ErrorState message={t('home.loadError')} onRetry={() => query.refetch()} />
      ) : !data || data.items.length === 0 ? (
        <div className="kx-home__todo-done">
          <span className="kx-home__todo-done-icon"><IconCheckCircle size={28} /></span>
          <p className="kx-home__todo-done-title">{t('home.todo.emptyTitle')}</p>
          <p className="kx-home__todo-done-desc">{t('home.todo.emptyDesc')}</p>
          <Button variant="secondary" onClick={() => navigate('/notifications')}>
            {t('home.todo.emptyCta')}
          </Button>
        </div>
      ) : (
        <>
          <ul className="kx-home__todo-list">
            {data.items.map((item, i) => (
              <li key={`${item.type}-${i}`}>
                <button
                  type="button"
                  className="kx-home__todo-row"
                  onClick={() => item.route && navigate(item.route)}
                >
                  <span className={`kx-home__legend-dot ${todoSeverityDot(item.severity)}`} aria-hidden="true" />
                  <span className="kx-home__todo-main">
                    <span className="kx-home__todo-title">
                      <span className={`kx-home__todo-tag is-${item.type}`}>
                        {todoTypeLabel(item.type, t)}
                      </span>
                      {item.title}
                    </span>
                    <span className="kx-home__todo-target">{item.target}</span>
                  </span>
                  <span className={`kx-home__todo-due tnum is-${item.severity}`}>
                    {todoDueText(item, t)}
                  </span>
                </button>
              </li>
            ))}
          </ul>
          {data.total > data.items.length && (
            <button type="button" className="kx-home__widget-more" onClick={() => navigate('/notifications')}>
              {t('home.viewAll')}
            </button>
          )}
        </>
      )}
    </section>
  );
}

/* ────────────────────────────────────────────────────────────
 * ④-우② 공지사항
 * ──────────────────────────────────────────────────────────── */
function isNew(createdAt: string): boolean {
  const d = new Date(createdAt).getTime();
  return Number.isFinite(d) && Date.now() - d < 3 * 86400000; // 3일 이내
}

function NoticeWidget() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const query = useQuery({
    queryKey: ['home-notices'],
    queryFn: () => noticeApi.list({ page: 0, size: 5 }),
  });
  const items = query.data?.items ?? [];

  return (
    <section className="kx-card kx-home__widget" aria-label={t('home.notice.title')}>
      <div className="kx-home__widget-head">
        <h2>
          <IconBell className="kx-title-ic" size={18} />
          {t('home.notice.title')}
        </h2>
      </div>
      {query.isLoading ? (
        <div className="kx-home__widget-body">
          {Array.from({ length: 4 }).map((_, i) => (
            <Skeleton key={i} height={20} />
          ))}
        </div>
      ) : query.isError ? (
        <ErrorState message={t('home.loadError')} onRetry={() => query.refetch()} />
      ) : items.length === 0 ? (
        <EmptyState
          title={t('home.notice.emptyTitle')}
          action={<Button variant="secondary" onClick={() => navigate('/work/notice')}>{t('home.notice.emptyCta')}</Button>}
        />
      ) : (
        <>
          <ul className="kx-home__notice-list">
            {items.map((n) => (
              <li key={n.id}>
                <button type="button" className="kx-home__notice-row" onClick={() => navigate('/work/notice')}>
                  {n.pinned && <span className="kx-home__pin" aria-label={t('home.notice.pinned')}>●</span>}
                  <span className="kx-home__notice-title">{n.title}</span>
                  {isNew(n.createdAt) && <span className="kx-home__new">{t('home.notice.new')}</span>}
                  <span className="kx-home__notice-date tnum">{(n.publishedAt ?? n.createdAt).slice(0, 10)}</span>
                </button>
              </li>
            ))}
          </ul>
          <button type="button" className="kx-home__widget-more" onClick={() => navigate('/work/notice')}>
            {t('home.notice.more')}
          </button>
        </>
      )}
    </section>
  );
}

/* ────────────────────────────────────────────────────────────
 * ④-우③ 빠른 작업 (정적 라우트)
 * ──────────────────────────────────────────────────────────── */
function QuickActions() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const eventId = useResolvedEventId();

  const actions: { key: string; Icon: typeof IconFloorplan; go: () => void }[] = [
    { key: 'editor', Icon: IconFloorplan, go: () => navigate(eventId ? `/events/${eventId}/halls/H7/layout` : '/schedule') },
    { key: 'invite', Icon: IconExhibitors, go: () => navigate(eventId ? `/events/${eventId}/dashboard` : '/schedule') },
    { key: 'docs', Icon: IconDocument, go: () => navigate('/docs') },
    { key: 'settlement', Icon: IconSettlement, go: () => navigate('/settlement') },
    { key: 'notice', Icon: IconBell, go: () => navigate('/work/notice') },
    { key: 'search', Icon: IconSearch, go: () => navigate('/work/search') },
  ];

  return (
    <section className="kx-card kx-home__widget" aria-label={t('home.quick.title')}>
      <div className="kx-home__widget-head">
        <h2>
          <IconBolt className="kx-title-ic" size={18} />
          {t('home.quick.title')}
        </h2>
      </div>
      <div className="kx-home__quick-grid">
        {actions.map((a) => (
          <button key={a.key} type="button" className="kx-home__quick-btn" onClick={a.go}>
            <span className="kx-home__quick-icon"><a.Icon size={22} /></span>
            <span className="kx-home__quick-label">{t(`home.quick.${a.key}`)}</span>
          </button>
        ))}
      </div>
    </section>
  );
}

/* ────────────────────────────────────────────────────────────
 * ④-우④ 최근 활동 (알림 피드 재사용)
 * ──────────────────────────────────────────────────────────── */
function relativeTime(iso: string, t: (k: string, o?: Record<string, unknown>) => string): string {
  const ts = new Date(iso).getTime();
  if (!Number.isFinite(ts)) return '';
  const diff = Math.max(0, Date.now() - ts);
  const min = Math.floor(diff / 60000);
  if (min < 1) return t('home.time.justNow');
  if (min < 60) return t('home.time.minutes', { n: min });
  const hr = Math.floor(min / 60);
  if (hr < 24) return t('home.time.hours', { n: hr });
  const day = Math.floor(hr / 24);
  return t('home.time.days', { n: day });
}

function ActivityWidget() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const query = useQuery({
    queryKey: ['home-activity'],
    queryFn: () => notificationApi.list({ page: 0, size: 5 }),
  });
  const items = query.data?.items ?? [];

  return (
    <section className="kx-card kx-home__widget" aria-label={t('home.activity.title')}>
      <div className="kx-home__widget-head">
        <h2>
          <IconClock className="kx-title-ic" size={18} />
          {t('home.activity.title')}
        </h2>
      </div>
      {query.isLoading ? (
        <div className="kx-home__widget-body">
          {Array.from({ length: 4 }).map((_, i) => (
            <Skeleton key={i} height={28} />
          ))}
        </div>
      ) : query.isError ? (
        <ErrorState message={t('home.loadError')} onRetry={() => query.refetch()} />
      ) : items.length === 0 ? (
        <EmptyState
          title={t('home.activity.emptyTitle')}
          action={<Button variant="secondary" onClick={() => navigate('/notifications')}>{t('home.activity.emptyCta')}</Button>}
        />
      ) : (
        <>
          <ul className="kx-home__activity-list">
            {items.map((n) => (
              <li key={n.id}>
                <button
                  type="button"
                  className="kx-home__activity-row"
                  onClick={() => navigate(n.link || '/notifications')}
                >
                  <span className={`kx-home__legend-dot ${n.readAt ? 'is-ended' : 'is-ongoing'}`} aria-hidden="true" />
                  <span className="kx-home__activity-text">{n.title}</span>
                  <span className="kx-home__activity-time tnum">{relativeTime(n.createdAt, t)}</span>
                </button>
              </li>
            ))}
          </ul>
          <button type="button" className="kx-home__widget-more" onClick={() => navigate('/notifications')}>
            {t('home.viewAll')}
          </button>
        </>
      )}
    </section>
  );
}

/* ────────────────────────────────────────────────────────────
 * ⑤ 내 행사 워크스페이스 (authStore.workspaces)
 * ──────────────────────────────────────────────────────────── */
function WorkspaceGrid() {
  const { t } = useTranslation();
  const navigate = useNavigate();
  const workspaces = useAuthStore((s) => s.workspaces);
  const selectEvent = useAuthStore((s) => s.selectEvent);
  const [inviteCode, setInviteCode] = useState('');

  function enter(eventId: string) {
    selectEvent(eventId);
    navigate(`/events/${eventId}/dashboard`);
  }

  return (
    <section className="kx-home__workspace" aria-label={t('home.workspace.title')}>
      <div className="kx-home__section-head">
        <h2 className="kx-home__section-title">
          <IconGrid className="kx-title-ic" size={18} />
          {t('home.workspace.title')}
        </h2>
      </div>
      {workspaces.length === 0 ? (
        <div className="kx-card kx-home__ws-empty">
          <div>
            <p className="kx-home__ws-empty-title">{t('home.workspace.emptyTitle')}</p>
            <p className="kx-home__ws-empty-desc">{t('home.workspace.emptyDesc')}</p>
          </div>
          <form
            className="kx-home__ws-invite"
            onSubmit={(e) => {
              e.preventDefault();
              if (inviteCode.trim()) navigate('/login');
            }}
          >
            <input
              className="kx-home__ws-code"
              value={inviteCode}
              onChange={(e) => setInviteCode(e.target.value)}
              placeholder={t('home.workspace.codePh')}
              aria-label={t('home.workspace.codePh')}
            />
            <Button type="submit" variant="secondary">
              {t('home.workspace.invite')}
            </Button>
          </form>
        </div>
      ) : (
        <div className="kx-home__ws-grid">
          {workspaces.map((ws) => (
            <button key={ws.eventId} type="button" className="kx-home__ws-card" onClick={() => enter(ws.eventId)}>
              <div className="kx-home__ws-card-top">
                <span className="kx-badge kx-badge--role">{roleLabel(ws.myRole, t)}</span>
                <DdayChip dday={ws.dday} />
              </div>
              <h3 className="kx-home__ws-card-title">{ws.eventName}</h3>
              <p className="kx-home__ws-card-meta tnum">
                {ws.startDate} – {ws.endDate}
              </p>
              <p className="kx-home__ws-card-hall">{ws.hallLabel}</p>
            </button>
          ))}
        </div>
      )}
    </section>
  );
}
