import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { List } from 'react-window';
import DubSegmentRow from './DubSegmentRow';
import { Table, Select } from '../ui';
import './DubSegmentTable.css';
const BASE_ROW_HEIGHT = 26;
const ROW_HEIGHT_WITH_ORIG = 40;
const COLUMNS = [
{ key: 'time', label: 'Time', width: 46 },
{ key: 'spkr', label: 'Spkr', width: 40 },
{ key: 'text', label: 'Text', flex: 1 },
{ key: 'lang', label: 'Lang', width: 38 },
{ key: 'voice', label: 'Voice', width: 56 },
{ key: 'vol', label: 'Vol', width: 36, title: 'Volume (0–200%)' },
{ key: 'act', label: '', width: 38 },
];
export default function DubSegmentTable({
segments, profiles, speakerClones, dubStep, dubProgress, previewLoadingId,
selectedIds, onSelect, onSelectAll, onClearSelection,
onEditField, onDelete, onRestore, onPreview, onSplit, onMerge, onDirect, onSeek,
}) {
const disabled = dubStep === 'generating' || dubStep === 'stopping';
const [query, setQuery] = useState('');
const [speakerFilter, setSpeakerFilter] = useState('');
// react-window v2 needs a concrete height prop — CSS 100 % doesn't cut it.
// Measure the body container and pass its height explicitly so the list
// renders every row that fits, not just a default-sized window.
const bodyRef = useRef(null);
const [bodyHeight, setBodyHeight] = useState(0);
useLayoutEffect(() => {
if (!bodyRef.current) return;
const measure = () => {
const h = bodyRef.current?.clientHeight || 0;
setBodyHeight((prev) => (Math.abs(prev - h) > 1 ? h : prev));
};
measure();
const ro = new ResizeObserver(measure);
ro.observe(bodyRef.current);
return () => ro.disconnect();
}, []);
const speakers = useMemo(() => {
const s = new Set(segments.map(x => x.speaker_id).filter(Boolean));
return Array.from(s).sort();
}, [segments]);
const filtered = useMemo(() => {
if (!query && !speakerFilter) return segments;
const q = query.trim().toLowerCase();
return segments.filter(s => {
if (speakerFilter && s.speaker_id !== speakerFilter) return false;
if (!q) return true;
return (s.text && s.text.toLowerCase().includes(q))
|| (s.text_original && s.text_original.toLowerCase().includes(q));
});
}, [segments, query, speakerFilter]);
const rowHeight = useCallback((index) => {
const s = filtered[index];
if (!s) return BASE_ROW_HEIGHT;
return (s.text_original && s.text_original !== s.text) ? ROW_HEIGHT_WITH_ORIG : BASE_ROW_HEIGHT;
}, [filtered]);
const rowProps = useMemo(() => ({
filtered, profiles, speakerClones, disabled, dubStep, dubProgress, previewLoadingId,
selectedIds, onSelect, onEditField, onDelete, onRestore, onPreview, onSplit, onMerge, onDirect, onSeek,
segments,
}), [filtered, profiles, speakerClones, disabled, dubStep, dubProgress, previewLoadingId,
selectedIds, onSelect, onEditField, onDelete, onRestore, onPreview, onSplit, onMerge, onDirect, onSeek, segments]);
const Row = useCallback(({ index, style, filtered: fl, profiles: profs, speakerClones: clones, disabled: dis, dubProgress: prog, dubStep: step, previewLoadingId: previewId, selectedIds: sel, onSelect: pick, onEditField: edit, onDelete: del, onRestore: rest, onPreview: prev, onSplit: split, onMerge: merge, onDirect: direct, onSeek: seek, segments: segs }) => {
const seg = fl[index];
if (!seg) return null;
const absoluteIndex = segs.indexOf(seg);
const isActive = (step === 'generating' || step === 'stopping') && prog.current === absoluteIndex + 1;
const isDone = (step === 'generating' || step === 'stopping') && prog.current > absoluteIndex + 1;
const canMerge = index < fl.length - 1;
return (