import React from 'react';
import {
Check,
Zap,
Layout,
Trophy
} from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { motion } from 'framer-motion';
import type { Zone, InternalRegion, UserSelection, FrameCandidate, SlidePlan } from '../types/designAgent';
import { getSectionsForZone } from '../utils/slidePlanUtils';
interface FramePanelProps {
slidePlan: SlidePlan | null;
selectedZone: Zone | null;
selectedRegion: InternalRegion | null;
userSelection: UserSelection;
onFrameSelect: (frameId: string) => void;
onNoDesignToggle: () => void;
}
export default function FramePanel({
slidePlan,
selectedZone,
selectedRegion,
userSelection,
onFrameSelect,
}: FramePanelProps) {
// Zone 중심 컨셉: 선택된 존에 할당된 섹션들의 프레임 후보들을 수집
const assignedSectionIds = selectedZone ? getSectionsForZone(selectedZone, userSelection) : [];
// 해당 섹션들이 포함된 원본 리전들에서 후보군 추출
const candidates: FrameCandidate[] = React.useMemo(() => {
if (!slidePlan || !selectedZone) return [];
// 단순화: 선택된 존의 첫 번째 리전의 후보군을 우선 사용 (Phase Z MVP1 기준)
// 실제로는 할당된 섹션에 따라 동적으로 필터링된 후보군이 필요함
const targetRegion = selectedRegion || selectedZone.internal_regions[0];
return targetRegion?.frame_candidates || [];
}, [slidePlan, selectedZone, selectedRegion]);
const currentFrameId = React.useMemo(() => {
const targetRegion = selectedRegion || selectedZone?.internal_regions[0];
if (!targetRegion) return null;
return userSelection.overrides.zone_frames[targetRegion.id] || targetRegion.frame_match_strategy.frame_id;
}, [selectedZone, selectedRegion, userSelection.overrides.zone_frames]);
if (!selectedZone) {
return (
Select a Zone
to View Designs
);
}
return (
{/* Header */}
Design Wheel
TOP {candidates.length}
현재 구역의 섹션({assignedSectionIds.join(', ')})에 최적화된 추천 디자인입니다.
{/* Vertical Wheel List */}
{candidates.length === 0 ? (
) : (
candidates.map((candidate, index) => {
const isSelected = currentFrameId === candidate.id;
const isReject = candidate.label === "reject";
// catalog 미등록 = backend Step 7-A 가 override 시도해도 skip.
// catalogRegistered === false 만 체크 (undefined = 정보 없음, 일반 처리).
const isCatalogMissing = candidate.catalogRegistered === false;
// ─── IMP-29 u3 — IMP-05 L2 candidate_evidence surface ───────────
// All evidence fields optional; silent degradation when undefined
// (pre-IMP-05 fixtures fall back to label/catalogRegistered only).
const isFilteredDirect = candidate.filteredForDirectExecution === true;
const hasDecision = candidate.decision === "selected" || candidate.decision === "skipped";
const isSkipped = candidate.decision === "skipped";
const isSelectedDecision = candidate.decision === "selected";
const showRouteChip =
candidate.routeHint && candidate.routeHint !== "direct_render";
const showStatusChip =
candidate.phaseZStatus && candidate.phaseZStatus !== "auto_renderable";
const hasCapacityFit =
candidate.capacityFit && candidate.capacityFit.fit_status;
const capacityMismatch =
hasCapacityFit && candidate.capacityFit!.fit_status !== "ok";
// Compose evidence tooltip lines (only when at least one signal present).
const evidenceLines: string[] = [];
if (candidate.decision) evidenceLines.push(`decision: ${candidate.decision}`);
if (candidate.reason) evidenceLines.push(`reason: ${candidate.reason}`);
if (candidate.routeHint) evidenceLines.push(`route: ${candidate.routeHint}`);
if (candidate.phaseZStatus)
evidenceLines.push(`phase_z_status: ${candidate.phaseZStatus}`);
if (hasCapacityFit) {
const cf = candidate.capacityFit!;
const capacityLine =
cf.fit_status === "ok"
? `capacity: ok${
typeof cf.item_count === "number"
? ` (items=${cf.item_count})`
: ""
}`
: `capacity: ${cf.fit_status}${
cf.mismatch_reason ? ` — ${cf.mismatch_reason}` : ""
}`;
evidenceLines.push(capacityLine);
}
const evidenceTooltip =
evidenceLines.length > 0 ? evidenceLines.join("\n") : undefined;
// Compose final tooltip: existing catalog/reject reasons first, then
// evidence detail (preserves Phase Q tooltip semantics).
const tooltipParts = [
isCatalogMissing
? "⚠ catalog 미등록 — render path 에서 적용 안 됨 (선택해도 backend 가 skip)"
: null,
isFilteredDirect
? "⚠ filtered_for_direct_execution — MVP1 직접 렌더 경로 제외"
: null,
isReject ? "V4 reject — render path 비추천" : null,
evidenceTooltip,
].filter((s): s is string => Boolean(s));
const composedTitle =
tooltipParts.length > 0 ? tooltipParts.join("\n\n") : undefined;
return (
);
})
)}
);
}