// v2 ANALYSIS stage — sources pane + 5-column kanban blocks + pinned board + mini-chat

const V2_TYPES = ['LITERAL','SENSORY','EMOTION','UNIVERSAL','METAPHOR'];

const V2SourcesPane = ({palette, selectedFile, setSelectedFile, onAnalyzeFile})=>{
  const [mode, setMode] = React.useState('tags');  // 'tags' | 'timeline' | 'visual'
  const [search, setSearch] = React.useState('');
  const [activeTags, setActiveTags] = React.useState(new Set(['all']));
  const files = palette.files || [];
  const visible = files.filter(f => !search || f.name.toLowerCase().includes(search.toLowerCase()));

  return (
    <aside style={{width:320,minWidth:320,borderRight:'1px solid var(--rule)',background:'var(--paper-2)',display:'flex',flexDirection:'column'}}>
      <div style={{padding:'14px 16px 10px',borderBottom:'1px solid var(--rule)'}}>
        <div className="tag" style={{color:'var(--muted)'}}>SOURCES · {files.length} files</div>
        <div style={{position:'relative',marginTop:10}}>
          <input value={search} onChange={e=>setSearch(e.target.value)}
            placeholder="search files…"
            style={{width:'100%',height:30,padding:'0 10px 0 28px',border:'1px solid var(--rule)',
              borderRadius:8,background:'var(--paper)',fontSize:12,fontFamily:'inherit',outline:'none'}}/>
          <div style={{position:'absolute',left:9,top:8.5,pointerEvents:'none'}}><V2Icon name="search" size={13} color="var(--muted)"/></div>
        </div>
        <div className="seg" style={{marginTop:10,width:'100%',justifyContent:'space-between'}}>
          {[['tags','Tags'],['timeline','Timeline'],['visual','Visual']].map(([k,l])=>(
            <button key={k} onClick={()=>setMode(k)} className={mode===k?'active':''} style={{flex:1,justifyContent:'center'}}>{l}</button>
          ))}
        </div>
      </div>
      {mode === 'tags' && (
        <V2SourcesTagsView files={visible} selectedFile={selectedFile} setSelectedFile={setSelectedFile}
          activeTags={activeTags} setActiveTags={setActiveTags}/>
      )}
      {mode === 'timeline' && (
        <V2SourcesTimelineView files={visible} selectedFile={selectedFile} setSelectedFile={setSelectedFile}/>
      )}
      {mode === 'visual' && (
        <V2SourcesVisualView files={visible} selectedFile={selectedFile} setSelectedFile={setSelectedFile}/>
      )}
    </aside>
  );
};

const V2SourcesTagsView = ({files, selectedFile, setSelectedFile, activeTags, setActiveTags}) => {
  // Auto-derive tags from folder names + kinds
  const tagCounts = { all: files.length };
  files.forEach(f => {
    const k = f.kind || 'doc';
    tagCounts[k] = (tagCounts[k]||0) + 1;
    const folder = (f.folder||'').replace(/^\d+\s*[·.]?\s*/,'').toLowerCase();
    if (folder) tagCounts[folder] = (tagCounts[folder]||0) + 1;
  });
  const allTags = Object.keys(tagCounts);
  const toggleTag = (tag) => setActiveTags(prev => {
    const next = new Set(prev);
    if (tag === 'all') return new Set(['all']);
    next.delete('all');
    if (next.has(tag)) next.delete(tag); else next.add(tag);
    if (next.size === 0) return new Set(['all']);
    return next;
  });
  const filtered = activeTags.has('all') ? files : files.filter(f => {
    const folder = (f.folder||'').replace(/^\d+\s*[·.]?\s*/,'').toLowerCase();
    return activeTags.has(f.kind) || activeTags.has(folder);
  });
  return (
    <>
      <div style={{padding:'10px 14px 6px',display:'flex',flexWrap:'wrap',gap:5}}>
        {allTags.map(tag => (
          <button key={tag} onClick={()=>toggleTag(tag)}
            className={`tagpill ${activeTags.has(tag)?'active':''}`}>
            {tag} <span className="count">{tagCounts[tag]}</span>
          </button>
        ))}
      </div>
      <div className="scroll" style={{flex:1,overflow:'auto',padding:'4px 8px 12px'}}>
        {filtered.map(f => <V2SourceFileRow key={f.id} f={f} active={f.id===selectedFile} onClick={()=>setSelectedFile(f.id)}/>)}
        {!filtered.length && <div style={{padding:'30px 14px',color:'var(--muted)',fontSize:11.5,textAlign:'center'}}>이 필터에 맞는 자료가 없어요.</div>}
      </div>
    </>
  );
};

const V2SourcesTimelineView = ({files, selectedFile, setSelectedFile}) => {
  // Sort by date asc; show with timeline rail.
  const ordered = [...files].sort((a,b) => (a.date||'').localeCompare(b.date||''));
  return (
    <div className="scroll" style={{flex:1,overflow:'auto',padding:'14px 16px'}}>
      <div className="tl-rail">
        {ordered.map((f) => {
          const meta = v2FileMeta(f.kind);
          const active = f.id === selectedFile;
          const dot = f.status === 'analyzing' ? 'live' : f.status === 'analyzed' ? 'done' : '';
          return (
            <div key={f.id} style={{position:'relative',marginBottom:14}}>
              <div className={`tl-dot ${dot}`}/>
              <button onClick={()=>setSelectedFile(f.id)}
                style={{display:'flex',alignItems:'center',gap:10,width:'100%',textAlign:'left',
                  padding:'10px 12px',background: active ? 'var(--primary-tint)' : 'var(--paper)',
                  border: active ? '1px solid var(--primary)' : '1px solid var(--rule)',
                  borderRadius: 'var(--radius-sm)',cursor:'pointer',fontFamily:'inherit'}}>
                <div style={{width:24,height:24,borderRadius:5,background:meta.tint,color:'#fff',
                  display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}}>
                  <V2Icon name={meta.icon} size={12}/>
                </div>
                <div style={{flex:1,minWidth:0}}>
                  <div style={{fontSize:12,fontWeight:500,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{f.name}</div>
                  <div className="mono" style={{fontSize:9.5,color:'var(--muted)',marginTop:2}}>{f.date||'—'} · {meta.label}</div>
                </div>
                <V2StatusPill status={f.status} progress={f.progress}/>
              </button>
            </div>
          );
        })}
        {!ordered.length && <div style={{padding:'30px 4px',color:'var(--muted)',fontSize:11.5,textAlign:'center'}}>업로드된 자료 없음.</div>}
      </div>
    </div>
  );
};

const V2SourcesVisualView = ({files, selectedFile, setSelectedFile}) => {
  return (
    <div className="scroll" style={{flex:1,overflow:'auto',padding:'10px',display:'grid',gridTemplateColumns:'1fr 1fr',gap:8,alignContent:'start'}}>
      {files.map(f => {
        const meta = v2FileMeta(f.kind);
        const active = f.id === selectedFile;
        const isMedia = f.kind === 'image' || f.kind === 'video';
        const url = isMedia ? window.API.fileRawUrl(f.id) : null;
        return (
          <button key={f.id} onClick={()=>setSelectedFile(f.id)}
            className={`thumb-card ${active?'active':''}`}
            style={{padding:0,display:'flex',flexDirection:'column',textAlign:'left',
              borderRadius:'var(--radius)',background:'var(--paper)',
              border: active ? '1px solid var(--primary)' : '1px solid var(--rule)',
              boxShadow: active ? '0 0 0 2px rgba(0,103,71,.18), var(--shadow-2)' : 'var(--shadow-1)',
              cursor:'pointer',overflow:'hidden',fontFamily:'inherit'}}>
            <div style={{height:90,background:isMedia?'var(--bg-2)':meta.tint,display:'flex',alignItems:'center',justifyContent:'center',position:'relative'}}>
              {isMedia && f.kind === 'image' && (
                <img src={url} alt="" loading="lazy" style={{width:'100%',height:'100%',objectFit:'cover'}}
                  onError={(e)=>{e.target.style.display='none';}}/>
              )}
              {isMedia && f.kind === 'video' && (
                <video src={url} preload="metadata" muted playsInline style={{width:'100%',height:'100%',objectFit:'cover'}}/>
              )}
              {!isMedia && <V2Icon name={meta.icon} size={26} color="rgba(255,255,255,.85)"/>}
            </div>
            <div style={{padding:'8px 10px'}}>
              <div style={{fontSize:11.5,fontWeight:active?600:500,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{f.name}</div>
              <div className="mono" style={{fontSize:9.5,color:'var(--muted)',marginTop:2}}>{meta.label}{f.size?` · ${f.size}`:''}</div>
            </div>
          </button>
        );
      })}
      {!files.length && <div style={{gridColumn:'1 / -1',padding:'30px 14px',color:'var(--muted)',fontSize:11.5,textAlign:'center'}}>업로드된 자료 없음.</div>}
    </div>
  );
};

const V2SourceFileRow = ({f, active, onClick}) => {
  const meta = v2FileMeta(f.kind);
  return (
    <button onClick={onClick} style={{
      display:'flex',alignItems:'center',gap:10,width:'100%',textAlign:'left',padding:'8px 10px',marginBottom:1,
      border:'none',background:active?'var(--primary-tint)':'transparent',
      borderRadius:6,cursor:'pointer',fontFamily:'inherit',
    }}>
      <div style={{width:28,height:28,borderRadius:6,background:meta.tint,color:'#fff',display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}}>
        <V2Icon name={meta.icon} size={14}/>
      </div>
      <div style={{flex:1,minWidth:0}}>
        <div style={{fontSize:12,fontWeight:active?600:500,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',color:active?'var(--primary)':'var(--ink)'}}>{f.name}</div>
        <div className="mono" style={{fontSize:10,color:'var(--muted)',marginTop:2,display:'flex',gap:6}}>
          <span>{meta.label}</span><span>·</span><span>{f.size||''}</span>
          {f.pages && <><span>·</span><span>{f.pages}p</span></>}
        </div>
      </div>
      <V2StatusPill status={f.status} progress={f.progress}/>
    </button>
  );
};

const V2AnalysisBlock = ({block, palette, projectId, onPin, onChipClick, onIdeate, onSaveSummary, pinnedSet})=>{
  const file = (palette.files || []).find(f => f.id === block.file);
  const meta = file ? v2FileMeta(file.kind) : v2FileMeta('doc');
  const [collapsed, setCollapsed] = React.useState(false);
  const [editing, setEditing] = React.useState(false);
  const [savingSummary, setSavingSummary] = React.useState(false);
  const [ideating, setIdeating] = React.useState(false);
  const [text, setText] = React.useState(block.summary);
  React.useEffect(() => setText(block.summary), [block.id, block.summary]);
  const editedByUser = block.summaryEditedBy === 'user';
  const commit = async () => {
    setEditing(false);
    if ((text||'') === (block.summary||'')) return;
    setSavingSummary(true);
    try { await onSaveSummary?.(block, text); }
    finally { setSavingSummary(false); }
  };

  return (
    <article style={{background:'var(--paper)',borderRadius:12,boxShadow:'var(--shadow-2)',
      border:'1px solid var(--rule)',overflow:'hidden',marginBottom:18}}>
      <header style={{display:'flex',alignItems:'flex-start',gap:14,padding:'14px 18px',
        borderBottom:'1px solid var(--rule)',background:'var(--paper-2)'}}>
        <V2PageThumb block={block}/>
        <div style={{flex:1,minWidth:0}}>
          <div style={{display:'flex',alignItems:'baseline',gap:10,marginBottom:4}}>
            <div style={{width:20,height:20,borderRadius:5,background:meta.tint,color:'#fff',display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}}><V2Icon name={meta.icon} size={10}/></div>
            <h3 style={{margin:0,fontSize:15,fontWeight:600}}>{block.title}</h3>
            <span className="mono" style={{fontSize:10,color:'var(--muted)'}}>
              {file?.name}{block.timecode ? ` · ${block.timecode}` : ''}
            </span>
            {savingSummary && (
              <span className="mono" style={{fontSize:9.5,color:'var(--accent)',display:'inline-flex',alignItems:'center',gap:5}}>
                <V2Spinner size="sm" color="var(--accent)"/> 저장 중…
              </span>
            )}
            <div style={{flex:1}}/>
            <span className="mono" style={{fontSize:9.5,color:'var(--muted)'}}>{(block.keywords || []).length} keywords</span>
          </div>
          {editing ? (
            <>
              <textarea autoFocus value={text} onChange={e=>setText(e.target.value)}
                onBlur={commit}
                onKeyDown={e=>{ if (e.key==='Escape') { setEditing(false); setText(block.summary); }
                                if (e.key==='Enter' && (e.metaKey||e.ctrlKey)) commit(); }}
                style={{width:'100%',minHeight:48,padding:'7px 9px',fontSize:13,lineHeight:1.5,
                  border:'1.5px solid var(--accent)',borderRadius:6,background:'#fff',
                  fontFamily:'inherit',resize:'vertical',outline:'none'}}/>
              <div className="mono" style={{fontSize:10,color:'var(--muted)',marginTop:4,display:'flex',gap:14}}>
                <span><kbd style={{fontFamily:'var(--font-mono)',fontSize:9.5,padding:'1px 5px',background:'var(--paper)',border:'1px solid var(--rule-2)',borderRadius:3}}>ESC</kbd> 취소</span>
                <span><kbd style={{fontFamily:'var(--font-mono)',fontSize:9.5,padding:'1px 5px',background:'var(--paper)',border:'1px solid var(--rule-2)',borderRadius:3}}>⌘ ↵</kbd> 저장</span>
              </div>
            </>
          ) : (
            <p onClick={()=>setEditing(true)} style={{margin:'2px 0 0',fontSize:13.5,lineHeight:1.55,
              color:'var(--ink-2)',cursor:'text',padding:'4px 0',borderRadius:4}}>
              <span className="mono" style={{fontSize:9,color:editedByUser?'var(--accent)':'var(--muted)',
                marginRight:8,letterSpacing:'.06em',fontWeight:700}}>
                {editedByUser ? 'EDITED BY YOU' : 'BY AI'}
              </span>
              {text || <span style={{color:'var(--muted)',fontStyle:'italic'}}>요약 없음 — 클릭해서 작성</span>}
            </p>
          )}
        </div>
        <div style={{display:'flex',gap:4,flexShrink:0}}>
          <button className={`btn sm ${ideating ? 'primary' : 'ghost'}`}
            onClick={()=>setIdeating(!ideating)}
            style={ideating?{}:{color:'var(--primary)'}}>
            <V2Icon name="wand" size={12} color={ideating?'#fff':undefined}/> Brainstorming
          </button>
          <button className="btn sm ghost" onClick={()=>setCollapsed(!collapsed)}>
            <V2Icon name={collapsed?'chevron-right':'chevron-down'} size={13}/>
          </button>
        </div>
      </header>
      {ideating && !collapsed && (
        <V2BlockIdeator block={block} projectId={projectId}
          onClose={()=>setIdeating(false)}
          onAdopt={(kw)=>onPin?.(kw)}
          pinnedSet={pinnedSet}/>
      )}
      {!collapsed && (
        <div style={{display:'grid',gridTemplateColumns:'repeat(5,1fr)',gap:1,background:'var(--rule)',padding:1}}>
          {V2_TYPES.map(t => {
            const items = (block.keywords || []).filter(k => k.type === t);
            return (
              <div key={t} style={{background:'var(--paper)',padding:'12px 12px 16px',minHeight:200}}>
                <div style={{display:'flex',alignItems:'center',gap:6,marginBottom:10,paddingBottom:8,
                  borderBottom: `2px solid var(--${t.toLowerCase()}-line)`}}>
                  <span className="mono" style={{fontSize:9.5,fontWeight:700,letterSpacing:'.08em'}}>{t}</span>
                  <span className="mono" style={{marginLeft:'auto',fontSize:9.5,color:'var(--muted)'}}>{items.length}</span>
                </div>
                {items.length === 0 && (
                  <div className="mono" style={{fontSize:10,color:'var(--muted)',padding:'10px 0',textAlign:'center'}}>—</div>
                )}
                <div style={{display:'flex',flexDirection:'column',gap:6}}>
                  {items.map(kw => (
                    <div key={kw.id} role="button" tabIndex={0} onClick={()=>onChipClick?.(kw)}
                      style={{
                        padding:'8px 10px',background:`var(--${t.toLowerCase()}-bg)`,
                        color:`var(--${t.toLowerCase()}-ink)`,border:'1px solid transparent',
                        borderRadius:6,cursor:'pointer',position:'relative'
                      }}
                      onMouseEnter={e=>{ e.currentTarget.style.boxShadow='var(--shadow-1)'; e.currentTarget.style.borderColor=`var(--${t.toLowerCase()}-line)`; }}
                      onMouseLeave={e=>{ e.currentTarget.style.boxShadow='none'; e.currentTarget.style.borderColor='transparent'; }}>
                      <div style={{fontSize:12,fontWeight:600,lineHeight:1.3}}>{kw.en}</div>
                      <div style={{fontSize:11,opacity:.75,marginTop:2}}>{kw.ko}</div>
                      {kw.gloss && <div style={{fontSize:10,opacity:.55,marginTop:5,lineHeight:1.4}}>{kw.gloss}</div>}
                      <button onClick={(e)=>{e.stopPropagation(); onPin?.(kw);}}
                        title={pinnedSet.has(kw.id)?'Unpin':'Pin'}
                        style={{position:'absolute',top:5,right:5,width:22,height:22,borderRadius:11,
                          border: `1px solid ${pinnedSet.has(kw.id)?'var(--accent)':'currentColor'}`,
                          cursor:'pointer',
                          background: pinnedSet.has(kw.id) ? 'var(--accent)' : 'rgba(255,255,255,.55)',
                          color: pinnedSet.has(kw.id) ? '#fff' : 'currentColor',
                          display:'flex',alignItems:'center',justifyContent:'center'}}>
                        <V2Icon name="pin" size={11} color={pinnedSet.has(kw.id)?'#fff':'currentColor'}/>
                      </button>
                    </div>
                  ))}
                </div>
              </div>
            );
          })}
        </div>
      )}
    </article>
  );
};

// === Inline BlockIdeator — opens under a block header ===========
// Lets the user expand/refine that block's keywords with presets or freeform
// prompts. Returned suggestions render as adopt-to-pin cards.
const V2BlockIdeator = ({block, projectId, onClose, onAdopt, pinnedSet}) => {
  const presets = [
    {key:'expand',  label:'Expand',     hint:'더 많은 키워드 발굴'},
    {key:'metaphor',label:'Metaphor↑',  hint:'은유로 압축'},
    {key:'sensory', label:'Sensory',    hint:'감각어로 풀기'},
    {key:'emotion', label:'Emotion',    hint:'감정 결로 이동'},
    {key:'twist',   label:'Twist',      hint:'클리셰 우회'},
  ];
  const presetPrompt = (k) => ({
    expand:   `이 블록의 키워드 결을 유지하면서 새 키워드 6개를 추가로 발굴해줘. 5층위(LITERAL/SENSORY/EMOTION/UNIVERSAL/METAPHOR)에 골고루.`,
    metaphor: `이 블록의 핵심을 한 줄 은유 키워드 4개로 압축해줘. 클리셰는 피하고, METAPHOR 층위로.`,
    sensory:  `이 블록을 시·청·촉·후·미각의 감각어로 다시 풀어 4개. SENSORY 층위로.`,
    emotion:  `이 블록의 정서·긴장을 EMOTION 층위 키워드 4개로 옮겨줘.`,
    twist:    `이 블록의 클리셰를 비틀어서 새 키워드 3개. METAPHOR/UNIVERSAL 층위 위주로.`,
  })[k] || '';

  const blockContext = React.useMemo(() => {
    const kwLine = (block.keywords || []).slice(0, 8).map(k => `${k.en} · ${k.ko}`).join(' / ');
    return `[Block: ${block.title}]\nSummary: ${block.summary || '(no summary)'}\nExisting keywords: ${kwLine}`;
  }, [block.id]);

  const [thread, setThread] = React.useState([
    {role:'ai', kind:'hint', text:'이 블록의 키워드를 어떤 결로 발전시킬까요? 프리셋을 누르거나 직접 물어보세요.'}
  ]);
  const [draft, setDraft] = React.useState('');
  const [thinking, setThinking] = React.useState(false);
  const [adopted, setAdopted] = React.useState(new Set());
  const scrollRef = React.useRef(null);

  React.useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [thread.length, thinking]);

  const send = async (text, presetKey) => {
    if (!text || thinking) return;
    const userMsg = presetKey
      ? {role:'user', kind:'preset', preset:presetKey, text}
      : {role:'user', kind:'text', text};
    const next = [...thread, userMsg];
    setThread(next);
    setThinking(true);
    setDraft('');
    try {
      const messages = [
        {me:false, text: blockContext},
        ...next.filter(m => m.role !== 'ai' || m.kind === 'suggestions')
              .map(m => ({me: m.role === 'user', text: m.text || (m.candidates||[]).map(c => c.en).join(', ')})),
      ];
      const res = await window.API.brainstorm(projectId, 'omni', messages);
      const items = (res.kws || []).map(k => ({
        id: `bi_${Date.now()}_${Math.random().toString(36).slice(2,6)}`,
        en: k.w || k.en || '',
        ko: k.ko || '',
        type: (k.tier || k.type || 'LITERAL').toUpperCase(),
        gloss: k.why || k.gloss || '',
        block_id: block.id,
        blockId: block.id,
        fileId: block.file,
        raw: k,
      }));
      setThread(prev => [...prev, {
        role:'ai', kind:'suggestions',
        items, note: (res.reply || '').split('\n').slice(0, 2).join(' ').slice(0, 200) || null,
      }]);
    } catch(e) {
      setThread(prev => [...prev, {role:'ai', kind:'hint', text:`에러: ${e.message}`}]);
    } finally { setThinking(false); }
  };

  const adopt = (item, key) => {
    if (adopted.has(key)) return;
    setAdopted(prev => new Set([...prev, key]));
    onAdopt?.(item);
  };

  return (
    <section style={{borderBottom:'1px solid var(--rule)',
      background:'linear-gradient(180deg, rgba(0,103,71,.04), rgba(0,103,71,.015))',
      padding:'14px 20px 16px'}}>
      <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:10}}>
        <V2Icon name="wand" size={13} color="var(--primary)"/>
        <span style={{fontSize:12.5,fontWeight:700,color:'var(--primary-2)'}}>Brainstorming · 이 블록의 키워드 발전시키기</span>
        <span className="mono" style={{fontSize:10,color:'var(--muted)',letterSpacing:'.04em'}}>· {block.title}</span>
        <div style={{flex:1}}/>
        <span className="mono" style={{fontSize:10,color:'var(--primary)',fontWeight:700}}>
          {adopted.size > 0 ? `${adopted.size} pinned` : 'Click + to pin to board'}
        </span>
        <button className="btn sm ghost" onClick={onClose} title="닫기"><V2Icon name="x" size={11}/></button>
      </div>

      <div style={{display:'flex',gap:5,marginBottom:10,flexWrap:'wrap'}}>
        {presets.map(p => (
          <button key={p.key} onClick={()=>send(presetPrompt(p.key), p.key)} disabled={thinking}
            style={{display:'inline-flex',alignItems:'baseline',gap:6,padding:'5px 11px',
              background:'#fff',border:'1px solid var(--rule)',borderRadius:999,
              fontSize:11,color:'var(--ink)',cursor: thinking?'wait':'pointer',
              fontFamily:'inherit'}}>
            <span style={{fontWeight:600}}>{p.label}</span>
            <span className="mono" style={{fontSize:9.5,color:'var(--muted)'}}>· {p.hint}</span>
          </button>
        ))}
      </div>

      <div ref={scrollRef} style={{maxHeight:300,overflow:'auto',
        background:'#fff',border:'1px solid var(--rule)',borderRadius:10,
        padding:'12px 14px',marginBottom:8}}>
        {thread.map((m, i) => (
          <div key={i} style={{marginBottom:10}}>
            {m.role === 'user' && (
              <div style={{display:'flex',gap:8,alignItems:'flex-start'}}>
                <span className="mono" style={{fontSize:9.5,color:'var(--primary)',fontWeight:700,letterSpacing:'.06em',width:36,paddingTop:3}}>YOU</span>
                <div style={{flex:1,fontSize:12.5,color:'var(--ink)',lineHeight:1.5,padding:'3px 0'}}>
                  {m.kind === 'preset' && (
                    <span className="mono" style={{fontSize:10,padding:'2px 7px',background:'rgba(0,103,71,.10)',
                      color:'var(--primary-2)',borderRadius:4,marginRight:6,fontWeight:700,letterSpacing:'.04em'}}>{m.preset.toUpperCase()}</span>
                  )}
                  {m.text}
                </div>
              </div>
            )}
            {m.role === 'ai' && m.kind === 'hint' && (
              <div style={{display:'flex',gap:8,alignItems:'flex-start'}}>
                <span className="mono" style={{fontSize:9.5,color:'var(--muted)',fontWeight:700,letterSpacing:'.06em',width:36,paddingTop:3}}>AI</span>
                <div style={{flex:1,fontSize:12.5,color:'var(--sub)',lineHeight:1.5,fontStyle:'italic',padding:'3px 0'}}>{m.text}</div>
              </div>
            )}
            {m.role === 'ai' && m.kind === 'suggestions' && (
              <div style={{display:'flex',gap:8,alignItems:'flex-start'}}>
                <span className="mono" style={{fontSize:9.5,color:'var(--muted)',fontWeight:700,letterSpacing:'.06em',width:36,paddingTop:3}}>AI</span>
                <div style={{flex:1,minWidth:0}}>
                  {m.note && <div style={{fontSize:12,color:'var(--sub)',lineHeight:1.5,marginBottom:8}}>{m.note}</div>}
                  <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:6}}>
                    {(m.items||[]).map((it, j) => {
                      const t = (it.type || 'LITERAL').toLowerCase();
                      const key = `${i}-${j}-${it.en}`;
                      const done = adopted.has(key) || pinnedSet?.has(it.id);
                      return (
                        <div key={key} style={{padding:'9px 10px',
                          background:`var(--${t}-bg)`,color:`var(--${t}-ink)`,
                          border:`1px solid ${done?'var(--primary)':'transparent'}`,
                          borderRadius:7,position:'relative',
                          opacity: done?.7:1,transition:'all .15s'}}>
                          <div style={{display:'flex',alignItems:'baseline',gap:6,marginBottom:2}}>
                            <span className="mono" style={{fontSize:9,fontWeight:700,letterSpacing:'.08em',opacity:.6}}>{it.type}</span>
                          </div>
                          <div style={{fontSize:12.5,fontWeight:600,lineHeight:1.25,paddingRight:24}}>{it.en}</div>
                          <div style={{fontSize:11.5,opacity:.75,marginTop:1,paddingRight:24}}>{it.ko}</div>
                          {it.gloss && <div style={{fontSize:10.5,opacity:.6,marginTop:4,lineHeight:1.4}}>{it.gloss}</div>}
                          <button onClick={()=>adopt(it, key)} disabled={done}
                            title={done ? '핀됨' : '보드에 핀'}
                            style={{position:'absolute',top:7,right:7,width:22,height:22,borderRadius:'50%',
                              border:`1px solid ${done?'var(--primary)':'rgba(0,0,0,.18)'}`,
                              background: done ? 'var(--primary)' : 'rgba(255,255,255,.85)',
                              color: done ? '#fff' : 'var(--ink)',
                              cursor: done ? 'default' : 'pointer',
                              display:'flex',alignItems:'center',justifyContent:'center',
                              transition:'all .12s'}}>
                            <V2Icon name={done?'check':'plus'} size={10} color={done?'#fff':'currentColor'}/>
                          </button>
                        </div>
                      );
                    })}
                  </div>
                </div>
              </div>
            )}
          </div>
        ))}
        {thinking && (
          <div style={{display:'flex',gap:8,alignItems:'center',padding:'4px 0'}}>
            <span className="mono" style={{fontSize:9.5,color:'var(--muted)',fontWeight:700,letterSpacing:'.06em',width:36}}>AI</span>
            <V2Spinner size="sm" color="var(--primary)"/>
            <span style={{fontSize:11.5,color:'var(--muted)',marginLeft:4}}>생각 중…</span>
          </div>
        )}
      </div>

      <div style={{display:'flex',gap:6,alignItems:'flex-end',
        background:'#fff',border:'1px solid var(--rule)',borderRadius:10,
        padding:'8px 8px 8px 12px'}}>
        <textarea value={draft} onChange={e=>setDraft(e.target.value)}
          onKeyDown={e=>{ if (e.key==='Enter' && (e.metaKey||e.ctrlKey)) { e.preventDefault(); send(draft); } }}
          placeholder="이 블록의 키워드를 어떻게 발전시킬지 자유롭게 물어보세요. (⌘↵ 보내기)"
          rows={1}
          style={{flex:1,border:'none',outline:'none',resize:'none',fontFamily:'inherit',
            fontSize:12.5,lineHeight:1.55,color:'var(--ink)',background:'transparent',
            padding:'5px 0',minHeight:22,maxHeight:90}}/>
        <button className="btn sm primary" onClick={()=>send(draft)} disabled={!draft.trim() || thinking}>
          <V2Icon name="send" size={11} color="#fff"/> 보내기
        </button>
      </div>
    </section>
  );
};

const V2PageThumb = ({block})=>{
  if (block.kind === 'scene') {
    return (
      <div style={{width:96,height:60,borderRadius:6,overflow:'hidden',position:'relative',
        background:'linear-gradient(140deg,#1a2030 0%,#3a3530 60%,#7a5028 100%)',flexShrink:0}}>
        <div style={{position:'absolute',inset:0,background:'radial-gradient(ellipse at 30% 40%,rgba(255,200,140,.25),transparent 60%)'}}/>
        <div style={{position:'absolute',left:6,bottom:5,display:'flex',alignItems:'center',gap:5}}>
          <div style={{width:14,height:14,borderRadius:7,background:'rgba(255,255,255,.9)',display:'flex',alignItems:'center',justifyContent:'center'}}>
            <V2Icon name="play" size={7} color="#1A1A1A"/>
          </div>
          {block.timecode && <span className="mono" style={{fontSize:8.5,color:'rgba(255,255,255,.85)'}}>{block.timecode}</span>}
        </div>
      </div>
    );
  }
  return (
    <div style={{width:96,height:120,background:'var(--paper)',borderRadius:5,
      boxShadow:'0 1px 0 rgba(0,0,0,.04), 0 3px 8px rgba(0,0,0,.06)',
      flexShrink:0,padding:'10px 9px',position:'relative',overflow:'hidden'}}>
      <div style={{height:5,background:'#1A1A1A',width:'40%',borderRadius:2,marginBottom:6}}/>
      {[0,1,2].map(i => (<div key={i} style={{height:2.5,background:'var(--rule)',marginBottom:3,borderRadius:2,width: i===0?'100%':i===1?'92%':'70%'}}/>))}
      <div style={{height:30,background:'var(--bg-2)',borderRadius:3,margin:'8px 0 6px'}}/>
      <div className="mono" style={{position:'absolute',bottom:5,right:7,fontSize:8,color:'var(--muted)'}}>p.{block.index}</div>
    </div>
  );
};

const V2BoardPane = ({palette, pinnedIds, onUnpin, onChipClick, mode, setMode, onGoIdeate})=>{
  const map = {};
  (palette.blocks || []).forEach(b => (b.keywords || []).forEach(k => map[k.id] = {...k, blockId: b.id, fileId: b.file}));
  const pinnedKws = pinnedIds.map(id => map[id]).filter(Boolean);
  return (
    <aside style={{width:380,minWidth:380,borderLeft:'1px solid var(--rule)',background:'var(--paper-2)',display:'flex',flexDirection:'column'}}>
      <div style={{display:'flex',alignItems:'center',gap:8,borderBottom:'1px solid var(--rule)',padding:'14px 16px',height:50}}>
        <V2Icon name="pin" size={14} color="var(--accent)"/>
        <div style={{fontSize:13,fontWeight:600}}>Pinned Board</div>
        <span className="mono" style={{fontSize:10,color:'var(--muted)'}}>{pinnedKws.length}</span>
        <div style={{flex:1}}/>
        <div style={{display:'flex',background:'var(--paper)',border:'1px solid var(--rule)',borderRadius:6,padding:2,gap:2}}>
          {['list','map'].map(m => (
            <button key={m} onClick={()=>setMode(m)} style={{
              padding:'3px 8px',fontSize:10,fontFamily:'var(--font-mono)',letterSpacing:'.06em',fontWeight:600,
              background: mode===m ? 'var(--ink)' : 'transparent', color: mode===m ? '#fff' : 'var(--muted)',
              border:'none',borderRadius:4,cursor:'pointer',textTransform:'uppercase'
            }}>{m}</button>
          ))}
        </div>
        <button className="btn primary sm" onClick={onGoIdeate} style={{padding:'4px 10px',fontSize:11}}>
          03 · IDEATE <V2Icon name="chevron-right" size={10} color="#fff"/>
        </button>
      </div>
      {mode === 'list' && (
        <div className="scroll" style={{flex:1,overflow:'auto',padding:'14px',display:'flex',flexDirection:'column',gap:8}}>
          {pinnedKws.map(kw => (
            <div key={kw.id} onClick={()=>onChipClick?.(kw)} style={{
              position:'relative',padding:'14px 16px',
              background:'var(--primary)',color:'#fff',
              borderRadius:12,border:'1px solid var(--primary-2)',cursor:'pointer',
              boxShadow:'0 2px 6px rgba(0,103,71,.25), inset 0 1px 0 rgba(255,255,255,.06)',
              overflow:'hidden'}}>
              <div style={{position:'absolute',top:8,left:14,right:14,height:2,
                backgroundImage:'radial-gradient(circle, rgba(255,182,0,.55) 1px, transparent 1.2px)',
                backgroundSize:'8px 2px',backgroundRepeat:'repeat-x',pointerEvents:'none'}}/>
              <div style={{display:'flex',alignItems:'flex-start',gap:8,marginTop:6}}>
                <div style={{flex:1,minWidth:0}}>
                  <div style={{fontSize:15,fontWeight:600,letterSpacing:'-.005em',lineHeight:1.25,color:'#fff'}}>{kw.en}</div>
                  <div style={{fontSize:12.5,color:'rgba(255,255,255,.78)',marginTop:2,fontWeight:500}}>{kw.ko}</div>
                </div>
                <button onClick={(e)=>{e.stopPropagation(); onUnpin?.(kw.id);}} style={{
                  width:20,height:20,padding:0,flex:'0 0 auto',
                  background:'rgba(255,255,255,.08)',border:'1px solid rgba(255,255,255,.18)',
                  borderRadius:'50%',cursor:'pointer',
                  display:'flex',alignItems:'center',justifyContent:'center',
                  color:'rgba(255,255,255,.7)'}}>
                  <V2Icon name="x" size={10} color="rgba(255,255,255,.7)"/>
                </button>
              </div>
              {kw.gloss ? (
                <div style={{fontSize:11.5,color:'rgba(255,255,255,.55)',
                  marginTop:8,paddingTop:8,borderTop:'1px solid rgba(255,255,255,.12)',
                  lineHeight:1.45,display:'flex',alignItems:'flex-end',gap:10}}>
                  <span style={{flex:1}}>{kw.gloss}</span>
                  <span className="mono" style={{fontSize:9.5,color:'rgba(255,255,255,.4)',
                    letterSpacing:'.06em',whiteSpace:'nowrap',flex:'0 0 auto'}}>{kw.fileId} · {kw.blockId}</span>
                </div>
              ) : (
                <div style={{marginTop:8,paddingTop:8,borderTop:'1px solid rgba(255,255,255,.12)',
                  display:'flex',justifyContent:'flex-end'}}>
                  <span className="mono" style={{fontSize:9.5,color:'rgba(255,255,255,.4)',
                    letterSpacing:'.06em'}}>{kw.fileId} · {kw.blockId}</span>
                </div>
              )}
            </div>
          ))}
          {pinnedKws.length === 0 && (
            <div style={{padding:'40px 20px',textAlign:'center',color:'var(--muted)',fontSize:12}}>
              아직 핀된 키워드가 없습니다. 왼쪽 칸반에서 핀 버튼을 누르세요.
            </div>
          )}
        </div>
      )}
      {mode === 'map' && <V2MindMap kws={pinnedKws} onChipClick={onChipClick} project={palette.project}/>}
    </aside>
  );
};

const V2MindMap = ({kws, onChipClick, project}) => {
  const W = 350, H = 380, cx = W/2, cy = H/2;
  const angleMap = {LITERAL:-Math.PI*0.92, SENSORY:-Math.PI*0.42, EMOTION:0, UNIVERSAL:Math.PI*0.42, METAPHOR:Math.PI*0.85};
  const groups = {LITERAL:[],SENSORY:[],EMOTION:[],UNIVERSAL:[],METAPHOR:[]};
  kws.forEach(k => groups[k.type]?.push(k));
  const positions = [];
  Object.entries(groups).forEach(([type, list]) => {
    if (!list.length) return;
    const baseAngle = angleMap[type];
    list.forEach((kw, i) => {
      const spread = (i - (list.length-1)/2) * 0.32;
      const angle = baseAngle + spread;
      const r = 130 + (i%2)*16;
      positions.push({kw, x: cx + Math.cos(angle)*r, y: cy + Math.sin(angle)*r});
    });
  });
  return (
    <div className="scroll" style={{flex:1,overflow:'auto',padding:'14px'}}>
      <div style={{position:'relative',width:'100%',height:H,background:'var(--paper)',
        border:'1px solid var(--rule)',borderRadius:10,overflow:'hidden'}}>
        <svg width="100%" height={H} viewBox={`0 0 ${W} ${H}`} style={{position:'absolute',inset:0}}>
          {positions.map((p,i) => (
            <line key={i} x1={cx} y1={cy} x2={p.x} y2={p.y} stroke="var(--rule-2)" strokeWidth="1" strokeDasharray="2 3" opacity="0.6"/>
          ))}
          <circle cx={cx} cy={cy} r="40" fill="var(--primary)" stroke="var(--secondary)" strokeWidth="1.5"/>
          <text x={cx} y={cy-4} textAnchor="middle" fill="var(--secondary)" fontSize="9" fontFamily="IBM Plex Mono" letterSpacing="0.08em" fontWeight="700">CAMPAIGN</text>
          <text x={cx} y={cy+10} textAnchor="middle" fill="#fff" fontSize="10" fontFamily="Pretendard" fontWeight="700">{(project?.name || '').slice(0,12)}</text>
        </svg>
        {positions.map((p,i) => {
          const t = p.kw.type.toLowerCase();
          return (
            <button key={p.kw.id+i} onClick={()=>onChipClick?.(p.kw)} style={{
              position:'absolute',
              left:`${(p.x/W)*100}%`,top:`${(p.y/H)*100}%`,transform:'translate(-50%,-50%)',
              padding:'5px 9px',background:`var(--${t}-bg)`,color:`var(--${t}-ink)`,
              border:'1px solid var(--rule-2)',borderRadius:14,fontSize:10.5,fontWeight:500,cursor:'pointer',
              boxShadow:'var(--shadow-1)',maxWidth:120,whiteSpace:'nowrap',overflow:'hidden',textOverflow:'ellipsis'
            }} title={`${p.kw.en} · ${p.kw.ko}`}>{p.kw.ko || p.kw.en}</button>
          );
        })}
        {Object.entries(angleMap).map(([type, ang]) => {
          const r = 168;
          const x = cx + Math.cos(ang)*r;
          const y = cy + Math.sin(ang)*r;
          return (
            <div key={type} style={{position:'absolute',
              left:`${(x/W)*100}%`,top:`${(y/H)*100}%`,
              transform:'translate(-50%,-50%)',
              fontFamily:'IBM Plex Mono',fontSize:8.5,fontWeight:700,
              letterSpacing:'.08em',color:'var(--muted)',pointerEvents:'none'}}>{type}</div>
          );
        })}
      </div>
      <div className="mono" style={{fontSize:9.5,color:'var(--muted)',marginTop:8,padding:'0 4px',
        display:'flex',justifyContent:'space-between'}}>
        <span>· 클릭 = mutate</span>
        <span>· 5층위가 캠페인을 둘러쌉니다</span>
      </div>
    </div>
  );
};

const V2MiniChat = ({kw, project, onClose, onPin, pinnedSet}) => {
  const [messages, setMessages] = React.useState([]);
  const [input, setInput] = React.useState('');
  const [thinking, setThinking] = React.useState(false);
  const scrollRef = React.useRef(null);

  React.useEffect(() => {
    setMessages([{role:'ai-greeting', text: `안녕하세요. "${kw.en} · ${kw.ko}"를 함께 다듬어볼까요? 아래 빠른 액션을 누르거나, 자유롭게 말씀해주세요.`}]);
    setInput(''); setThinking(false);
  }, [kw?.id]);
  React.useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [messages, thinking]);

  const respond = async (userText, action='sharper') => {
    const userMsg = {role:'user', text: userText};
    const next = [...messages.filter(m => m.role !== 'ai-greeting'), userMsg];
    setMessages(next); setThinking(true);
    try {
      const res = await window.API.mutateKeyword({
        file_id: kw.fileId || '',
        block_id: kw.blockId || kw.raw?.block_id || null,
        term: kw.en, ko: kw.ko, action,
        conversation: next.map(m => ({me: m.role==='user', text: m.text})),
      });
      setMessages(prev => [...prev, {role:'ai', explainer: res.reply || '', candidates: res.kws || []}]);
    } catch(e) {
      setMessages(prev => [...prev, {role:'ai', explainer: `에러: ${e.message}`, candidates: []}]);
    } finally { setThinking(false); }
  };

  const QUICK = [
    {key:'sharper', icon:'arrow-up', label:'Sharper', prompt:'한 단계 더 날카롭게'},
    {key:'opposite', icon:'arrow-swap', label:'Opposite', prompt:'반대 의미·대척점'},
    {key:'similar', icon:'approx', label:'Similar', prompt:'유사한 결의 키워드'},
    {key:'explain', icon:'arrow-down', label:'Explain', prompt:'어원·맥락 설명'},
  ];

  if (!kw) return null;
  return (
    <div style={{position:'absolute',top:0,right:0,bottom:0,width:480,background:'var(--paper)',borderLeft:'1px solid var(--rule)',
      boxShadow:'-12px 0 40px rgba(20,16,10,.08)',zIndex:50,display:'flex',flexDirection:'column'}}>
      <div style={{padding:'14px 18px',borderBottom:'1px solid var(--rule)',display:'flex',alignItems:'flex-start',gap:12}}>
        <div style={{width:32,height:32,borderRadius:8,background:'var(--ink)',color:'#F8F7F4',display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0}}>
          <V2Icon name="spark" size={14}/>
        </div>
        <div style={{flex:1,minWidth:0}}>
          <div className="mono" style={{fontSize:9.5,color:'var(--muted)',letterSpacing:'.08em',fontWeight:700}}>MUTATE · MINI-CHAT</div>
          <div style={{fontSize:14,fontWeight:600,marginTop:3}}>
            <span style={{opacity:.55,fontWeight:500,marginRight:6}}>on</span>
            {kw.en} <span style={{color:'var(--sub)',fontWeight:500}}>· {kw.ko}</span>
          </div>
          <div className="mono" style={{fontSize:9.5,color:'var(--muted)',marginTop:3}}>
            type: {kw.type}{kw.gloss?` · ${kw.gloss}`:''}
          </div>
        </div>
        <button className="btn sm ghost" onClick={onClose}><V2Icon name="x" size={13}/></button>
      </div>

      <div ref={scrollRef} className="scroll" style={{flex:1,overflow:'auto',padding:'18px 18px 8px',display:'flex',flexDirection:'column',gap:14}}>
        {messages.map((m,i) => {
          if (m.role === 'user') {
            return (<div key={i} style={{alignSelf:'flex-end',maxWidth:'80%',background:'var(--ink)',color:'#F8F7F4',
              padding:'9px 13px',borderRadius:'14px 14px 2px 14px',fontSize:12.5,lineHeight:1.5}}>{m.text}</div>);
          }
          if (m.role === 'ai-greeting') {
            return (
              <div key={i} style={{display:'flex',gap:10}}>
                <V2AIAvatar/>
                <p style={{margin:0,flex:1,fontSize:12.5,lineHeight:1.55,color:'var(--ink-2)',
                  background:'var(--bg-2)',padding:'10px 12px',borderRadius:'2px 14px 14px 14px'}}>{m.text}</p>
              </div>
            );
          }
          return (
            <div key={i} style={{display:'flex',gap:10}}>
              <V2AIAvatar/>
              <div style={{flex:1,minWidth:0}}>
                <p style={{margin:'0 0 10px',fontSize:12.5,lineHeight:1.55,color:'var(--ink-2)',
                  background:'var(--bg-2)',padding:'10px 12px',borderRadius:'2px 14px 14px 14px'}}>{m.explainer}</p>
                {(m.candidates || []).length > 0 && (
                  <div style={{display:'flex',flexDirection:'column',gap:6}}>
                    {m.candidates.map((c, j) => (
                      <div key={j} style={{display:'flex',gap:10,padding:'10px 12px',background:'var(--paper)',
                        border:'1px solid var(--rule)',borderRadius:10,alignItems:'flex-start'}}>
                        <div className={`stripe t-${(c.tier||c.type||'LITERAL').toUpperCase()}`} style={{minHeight:34}}/>
                        <div style={{flex:1,minWidth:0}}>
                          <div className="mono" style={{fontSize:9,color:'var(--muted)',fontWeight:700,letterSpacing:'.08em'}}>{(c.tier||c.type||'').toUpperCase()}</div>
                          <div style={{fontSize:13,fontWeight:500}}>{c.w || c.en}</div>
                          <div style={{fontSize:12,color:'var(--sub)'}}>{c.ko}</div>
                          {(c.why || c.gloss) && <div style={{fontSize:10.5,color:'var(--muted)',marginTop:4,lineHeight:1.45,fontStyle:'italic'}}>"{c.why || c.gloss}"</div>}
                        </div>
                        <button onClick={()=>onPin?.({...c, tier: c.tier || c.type, type: (c.tier||c.type||'LITERAL').toUpperCase(), en: c.w || c.en, w: c.w || c.en})}
                          className="btn sm" style={{flexShrink:0,padding:'0 8px'}}><V2Icon name="pin" size={10}/></button>
                      </div>
                    ))}
                  </div>
                )}
              </div>
            </div>
          );
        })}
        {thinking && (
          <div style={{display:'flex',gap:10}}>
            <V2AIAvatar/>
            <div style={{padding:'10px 12px',background:'var(--bg-2)',borderRadius:'2px 14px 14px 14px',display:'flex',gap:4}}>
              <V2Dot delay={0}/><V2Dot delay={.15}/><V2Dot delay={.3}/>
            </div>
          </div>
        )}
      </div>

      {messages.filter(m => m.role==='user').length === 0 && (
        <div style={{padding:'4px 14px 0',display:'grid',gridTemplateColumns:'1fr 1fr',gap:6}}>
          {QUICK.map(q => (
            <button key={q.key} onClick={()=>respond(q.prompt, q.key)} style={{
              display:'flex',alignItems:'center',gap:8,padding:'8px 10px',
              background:'var(--paper-2)',border:'1px solid var(--rule)',borderRadius:8,
              cursor:'pointer',textAlign:'left',fontFamily:'inherit'
            }}>
              <V2Icon name={q.icon} size={12} color="var(--accent)"/>
              <span style={{fontSize:11.5,fontWeight:500}}>{q.label}</span>
              <span className="mono" style={{fontSize:9.5,color:'var(--muted)',marginLeft:'auto'}}>{q.prompt}</span>
            </button>
          ))}
        </div>
      )}

      <div style={{padding:'12px 14px 14px',borderTop:'1px solid var(--rule)',background:'var(--paper-2)'}}>
        <div style={{display:'flex',gap:8,alignItems:'flex-end',padding:'4px 4px 4px 12px',background:'var(--paper)',
          border:'1px solid var(--rule-2)',borderRadius:12}}>
          <textarea value={input} onChange={e=>setInput(e.target.value)}
            onKeyDown={e=>{ if (e.key==='Enter' && !e.shiftKey) { e.preventDefault(); if (input.trim()) { respond(input.trim(), 'custom'); setInput(''); } } }}
            placeholder="더 촉각적으로 / 레트로 감성으로 / 일본어 톤으로…"
            rows={1} style={{flex:1,border:'none',outline:'none',padding:'8px 0',fontSize:12.5,
              lineHeight:1.5,background:'transparent',fontFamily:'inherit',resize:'none',maxHeight:80}}/>
          <button className="btn primary sm" onClick={()=>{ if (input.trim()) { respond(input.trim(), 'custom'); setInput(''); } }}
            style={{height:28}}><V2Icon name="send" size={11}/></button>
        </div>
      </div>
    </div>
  );
};

const V2AIAvatar = () => (<div style={{width:24,height:24,borderRadius:12,background:'var(--ink)',color:'#F8F7F4',display:'flex',alignItems:'center',justifyContent:'center'}}><V2Icon name="spark" size={11}/></div>);
const V2Dot = ({delay}) => (<span style={{width:6,height:6,borderRadius:3,background:'var(--muted)',animation:'pulse 1.2s ease-in-out infinite',animationDelay:`${delay}s`}}/>);

// === v3.4 view modes: By theme + By quote ===

const V2ThemeView = ({blocks, pinnedSet, onPin, onChipClick}) => {
  // Group keywords by simple co-occurrence buckets — five tier-themed groups,
  // each pulling that tier's keywords across all blocks.
  const tiers = [
    {key:'METAPHOR',  num:'01', title:'한 줄의 은유',  en:'Compressed metaphor'},
    {key:'UNIVERSAL', num:'02', title:'보편적 가치',   en:'Universal value'},
    {key:'EMOTION',   num:'03', title:'정서·긴장',     en:'Affect'},
    {key:'SENSORY',   num:'04', title:'감각의 디테일', en:'Sensory'},
    {key:'LITERAL',   num:'05', title:'표면 단어',     en:'Surface'},
  ];
  const map = {};
  blocks.forEach(b => (b.keywords||[]).forEach(k => map[k.id] = {...k, blockId: b.id, fileId: b.file}));
  const groupedByTier = {};
  Object.values(map).forEach(k => { (groupedByTier[k.type] ||= []).push(k); });

  return (
    <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:18}}>
      {tiers.map(t => {
        const kws = groupedByTier[t.key] || [];
        if (!kws.length) return null;
        return (
          <article key={t.key} className="frame" style={{padding:'20px 22px'}}>
            <div style={{display:'flex',alignItems:'baseline',gap:10,marginBottom:14}}>
              <span className="mono" style={{fontSize:9.5,color:'var(--primary)',fontWeight:700,letterSpacing:'.1em'}}>{t.num}</span>
              <h3 style={{margin:0,fontSize:18,fontWeight:700,letterSpacing:'-.015em'}}>{t.title}</h3>
              <span className="mono" style={{fontSize:10,color:'var(--muted)'}}>· {t.en}</span>
              <span className="mono" style={{marginLeft:'auto',fontSize:10,color:'var(--primary)',fontWeight:700,
                padding:'2px 7px',background:'rgba(0,103,71,.10)',borderRadius:4}}>{kws.length} KW</span>
            </div>
            <div style={{display:'flex',flexWrap:'wrap',gap:6}}>
              {kws.map(kw => (
                <span key={kw.id} className={`chip t-${kw.type}`}
                  onClick={()=>onChipClick?.(kw)}
                  style={{outline: pinnedSet.has(kw.id) ? '2px solid var(--primary)' : 'none', outlineOffset:'1px'}}>
                  <span className="chip-en">{kw.en}</span>
                  <span className="chip-ko">·{kw.ko}</span>
                  <button onClick={(e)=>{e.stopPropagation(); onPin?.(kw);}}
                    className="btn ghost xs" style={{padding:'0 4px',marginLeft:2,height:'auto'}}>
                    <V2Icon name="pin" size={9} color={pinnedSet.has(kw.id)?'var(--primary)':'var(--muted)'}/>
                  </button>
                </span>
              ))}
            </div>
          </article>
        );
      })}
    </div>
  );
};

const V2QuoteView = ({blocks, pinnedSet, onPin, onChipClick}) => {
  const TIERS = ['LITERAL','SENSORY','EMOTION','UNIVERSAL','METAPHOR'];
  return (
    <div style={{display:'flex',flexDirection:'column',gap:14}}>
      <div className="mono" style={{fontSize:10,color:'var(--muted)',letterSpacing:'.08em',marginBottom:2}}>
        ORIGINAL EXCERPT  →  DERIVED KEYWORDS · 원문에서 직접 길어 올린 키워드
      </div>
      {blocks.map((b, i) => (
        <article key={b.id} className="frame" style={{
          display:'grid',gridTemplateColumns:'minmax(0,1.05fr) minmax(0,1fr)',overflow:'hidden'}}>
          <div style={{padding:'24px 26px',borderRight:'1px solid var(--rule)',
            background:'linear-gradient(180deg, rgba(0,103,71,.04) 0%, transparent 100%)',position:'relative'}}>
            <div style={{display:'flex',alignItems:'baseline',gap:8,marginBottom:10}}>
              <span className="mono" style={{fontSize:10,color:'var(--primary)',fontWeight:700,letterSpacing:'.08em'}}>
                №{String(i+1).padStart(2,'0')}
              </span>
              <span className="mono" style={{fontSize:10,color:'var(--muted)',letterSpacing:'.06em'}}>{b.title}</span>
              {b.timecode && <span className="mono" style={{fontSize:10,color:'var(--muted)'}}>· {b.timecode}</span>}
            </div>
            <div style={{position:'absolute',left:12,top:42,fontFamily:'serif',fontSize:56,
              color:'var(--secondary)',opacity:.55,lineHeight:1,userSelect:'none',fontWeight:700}}>"</div>
            <p style={{margin:'0 0 12px 18px',fontSize:16,lineHeight:1.55,color:'var(--ink)',
              fontStyle:'italic',fontWeight:500}}>{b.excerpt}</p>
            <div style={{marginLeft:18,paddingTop:10,borderTop:'1px dashed var(--rule)',
              fontSize:12.5,lineHeight:1.5,color:'var(--sub)'}}>
              <span className="mono" style={{fontSize:9,color:'var(--primary)',letterSpacing:'.08em',marginRight:6,fontWeight:700}}>SUMMARY</span>
              {b.summary}
            </div>
          </div>
          <div style={{padding:'24px 26px',display:'flex',flexDirection:'column',gap:10}}>
            <div className="mono" style={{fontSize:10,color:'var(--muted)',letterSpacing:'.08em',display:'flex',justifyContent:'space-between'}}>
              <span>KEYWORDS DERIVED</span>
              <span style={{color:'var(--primary)',fontWeight:700}}>{(b.keywords||[]).length} found</span>
            </div>
            {TIERS.map(t => {
              const kws = (b.keywords||[]).filter(k => k.type === t);
              if (!kws.length) return null;
              return (
                <div key={t} style={{display:'grid',gridTemplateColumns:'72px 1fr',gap:10,alignItems:'start'}}>
                  <div className="mono" style={{fontSize:9,letterSpacing:'.1em',color:'var(--muted)',fontWeight:700,
                    paddingTop:6,borderTop: t==='METAPHOR' ? '2px solid var(--primary)' : '1px solid var(--rule)'}}>
                    {t}
                  </div>
                  <div style={{display:'flex',flexWrap:'wrap',gap:5,paddingTop:4}}>
                    {kws.map(k => (
                      <span key={k.id} className={`chip t-${k.type}`}
                        onClick={()=>onChipClick?.({...k, blockId: b.id, fileId: b.file})}
                        style={{outline: pinnedSet.has(k.id) ? '2px solid var(--primary)' : 'none', outlineOffset:'1px'}}>
                        <span className="chip-en">{k.en}</span>
                        <span className="chip-ko">·{k.ko}</span>
                      </span>
                    ))}
                  </div>
                </div>
              );
            })}
          </div>
        </article>
      ))}
    </div>
  );
};

const V2AnalysisStage = ({palette, project, pinnedIds, onPin, onUnpin, onSaveSummary, onAnalyzeFile, onGoIdeate}) => {
  const [selectedFile, setSelectedFile] = React.useState((palette.files||[])[0]?.id);
  const [boardMode, setBoardMode] = React.useState('list');
  const [view, setView] = React.useState('per-file');
  const [activeKw, setActiveKw] = React.useState(null);
  const pinnedSet = React.useMemo(() => new Set(pinnedIds), [pinnedIds]);
  const blocks = (palette.blocks || []).filter(b => !selectedFile || b.file === selectedFile);
  const allBlocks = palette.blocks || [];

  return (
    <div className="v2-app" style={{display:'flex',height:'100%',background:'var(--bg)',position:'relative'}}>
      <V2SourcesPane palette={palette} selectedFile={selectedFile} setSelectedFile={setSelectedFile} onAnalyzeFile={onAnalyzeFile}/>
      <main className="scroll" style={{flex:1,overflow:'auto',display:'flex',flexDirection:'column'}}>
        <div style={{padding:'18px 32px 14px',borderBottom:'1px solid var(--rule)',
          background:'rgba(255,255,255,.75)',backdropFilter:'blur(12px) saturate(1.1)',
          WebkitBackdropFilter:'blur(12px) saturate(1.1)',position:'sticky',top:0,zIndex:2}}>
          <div className="seg">
            {[['per-file','Per file'],['by-theme','By theme'],['by-quote','By quote']].map(([k,l])=>(
              <button key={k} onClick={()=>setView(k)} className={view===k?'active':''}>{l}</button>
            ))}
          </div>
        </div>
        <div className="scroll" style={{flex:1,padding:'24px 28px 80px',overflow:'auto'}}>
          {blocks.length === 0 && view === 'per-file' && (
            <div style={{padding:'80px 20px',textAlign:'center',color:'var(--muted)',fontSize:13}}>
              이 파일은 아직 분석되지 않았습니다. 파일 카드의 <b>Analyze</b> 버튼을 눌러주세요.
              <button className="btn sm primary" onClick={()=>onAnalyzeFile?.(selectedFile)} style={{marginLeft:14}}>
                <V2Icon name="sparkles" size={11}/> 지금 분석
              </button>
            </div>
          )}
          {view === 'per-file' && blocks.map(block => (
            <V2AnalysisBlock key={block.id} block={block} palette={palette}
              projectId={project?.id}
              pinnedSet={pinnedSet}
              onPin={kw => onPin?.(kw)}
              onChipClick={kw => setActiveKw(kw)}
              onIdeate={() => onGoIdeate?.()}
              onSaveSummary={onSaveSummary}/>
          ))}
          {view === 'by-theme' && (
            <V2ThemeView blocks={allBlocks} pinnedSet={pinnedSet}
              onPin={kw => onPin?.(kw)} onChipClick={kw => setActiveKw(kw)}/>
          )}
          {view === 'by-quote' && (
            <V2QuoteView blocks={allBlocks} pinnedSet={pinnedSet}
              onPin={kw => onPin?.(kw)} onChipClick={kw => setActiveKw(kw)}/>
          )}
        </div>
      </main>
      <V2BoardPane palette={palette} pinnedIds={pinnedIds} onUnpin={onUnpin}
        onChipClick={kw => setActiveKw(kw)} mode={boardMode} setMode={setBoardMode}
        onGoIdeate={onGoIdeate}/>
      {activeKw && (
        <V2MiniChat kw={activeKw} project={project}
          onClose={()=>setActiveKw(null)}
          onPin={c => onPin?.(c)}
          pinnedSet={pinnedSet}/>
      )}
    </div>
  );
};

window.V2AnalysisStage = V2AnalysisStage;
