import React, { memo, useRef } from 'react';
import {
  CheckCircle, AlertCircle, Circle, Trash2, Loader, Headphones, Scissors, Merge,
  MoreHorizontal, Sparkles,
} from 'lucide-react';
import { formatTime } from '../utils/format';
import { LANG_CODES } from '../utils/languages';
import { PRESETS } from '../utils/constants';
import { Menu, Button, Badge } from '../ui';
import './DubSegmentRow.css';

const CHAR_BUDGET_RATIO = 1.3;
const SENTENCE_END = /[.!?。！？]/;

function rowClass(isActive, isDone, selected) {
  return `segment-row${isActive ? ' segment-active' : ''}${isDone ? ' segment-done' : ''}${selected ? ' segment-selected' : ''}`;
}

// Best split point for the Scissors menu when the user hasn't placed a cursor —
// prefer the sentence boundary nearest the middle, then a whitespace boundary,
// then the literal midpoint as a last resort.
function bestSplitPoint(text) {
  const mid = Math.floor(text.length / 2);
  let best = -1, bestDist = Infinity;
  for (let i = 0; i < text.length; i++) {
    if (SENTENCE_END.test(text[i])) {
      const d = Math.abs(i + 1 - mid);
      if (d < bestDist) { best = i + 1; bestDist = d; }
    }
  }
  if (best > 0 && best < text.length) return best;
  for (let r = 0; r < text.length; r++) {
    for (const i of [mid - r, mid + r]) {
      if (i > 0 && i < text.length && /\s/.test(text[i])) return i;
    }
  }
  return mid;
}

// Accept "m:ss[.s]" or raw seconds; return null on garbage so the field can revert.
function parseTime(s) {
  const m = /^\s*(\d+):([0-5]?\d(?:\.\d+)?)\s*$/.exec(s);
  if (m) return parseInt(m[1], 10) * 60 + parseFloat(m[2]);
  const n = parseFloat(s);
  return Number.isFinite(n) ? n : null;
}

function DubSegmentRow({
  seg, idx, style, disabled, isActive, isDone, previewLoading, selected,
  profiles, speakerClones, onEditField, onDelete, onRestore, onPreview, onSelect, onSplit, onMerge, canMerge,
  onDirect, onSeek,
}) {
  const textInputRef = useRef(null);
  // Remember where the caret was inside the text field even after it loses
  // focus — Radix's menu trigger steals focus, so by the time the Scissors
  // item fires, selectionStart on the input would otherwise read as null.
  const lastCursorRef = useRef(null);
  const speakerOptions = speakerClones ? Object.keys(speakerClones) : [];
  const speakerListId = `seg-speakers-${seg.id}`;
  const syncColor = seg.sync_ratio === undefined ? null
    : (seg.sync_ratio >= 0.95 && seg.sync_ratio <= 1.05) ? '#b8bb26'
    : seg.sync_ratio > 1.25 ? '#fb4934'
    : '#fabd2f';
  const SyncIcon = seg.sync_ratio === undefined ? null
    : (seg.sync_ratio >= 0.95 && seg.sync_ratio <= 1.05) ? CheckCircle
    : seg.sync_ratio > 1.25 ? AlertCircle
    : Circle;

  const overBudget = seg.text_original
    && seg.text.length > Math.ceil(seg.text_original.length * CHAR_BUDGET_RATIO);

  const handleTextKeyDown = (e) => {
    // Ctrl/Cmd+D → split at cursor, Ctrl/Cmd+M → merge with next
    if ((e.ctrlKey || e.metaKey) && (e.key === 'd' || e.key === 'D')) {
      e.preventDefault();
      const pos = e.target.selectionStart ?? seg.text.length;
      onSplit(seg.id, pos);
    } else if ((e.ctrlKey || e.metaKey) && (e.key === 'm' || e.key === 'M')) {
      e.preventDefault();
      if (canMerge) onMerge(seg.id);
    }
  };

  const captureCursor = (e) => {
    const pos = e.target.selectionStart;
    if (pos != null) lastCursorRef.current = pos;
  };

  // Row click → seek the player to this segment. We don't fire when the click
  // originates in an interactive element (input, button, the actions cluster)
  // so per-field editing keeps working.
  const handleRowClick = (e) => {
    if (!onSeek) return;
    if (e.target.closest('input, button, select, textarea, label, [data-noseek]')) return;
    onSeek(seg.start);
  };

  return (
    <div style={style} className={rowClass(isActive, isDone, selected)} onClick={handleRowClick}>
      <input
        type="checkbox"
        checked={!!selected}
        onChange={(e) => onSelect(seg.id, idx, e.nativeEvent.shiftKey)}
        onClick={(e) => onSelect(seg.id, idx, e.shiftKey)}
        disabled={disabled}
        style={{ accentColor: '#d3869b' }}
        className="seg-check"
        title="Select segment (shift+click for range)"
      />
      <span className="segment-time seg-time">
        <span className="seg-time-row">
          <input
            type="text"
            className="seg-time-input"
            defaultValue={formatTime(seg.start)}
            key={`start-${seg.id}-${seg.start}`}
            disabled={disabled}
            title="Click to edit start time (m:ss.s). Enter to commit, Esc to cancel."
            onClick={(e) => e.stopPropagation()}
            onKeyDown={(e) => {
              if (e.key === 'Enter') e.target.blur();
              if (e.key === 'Escape') { e.target.value = formatTime(seg.start); e.target.blur(); }
            }}
            onBlur={(e) => {
              const v = parseTime(e.target.value);
              if (v == null || v < 0 || v >= seg.end) {
                e.target.value = formatTime(seg.start);
                return;
              }
              if (Math.abs(v - seg.start) > 1e-3) {
                onEditField(seg.id, 'start', +v.toFixed(3));
              } else {
                e.target.value = formatTime(seg.start);
              }
            }}
          />
          <span className="seg-time-sep">–</span>
          <span className="seg-time-end">{formatTime(seg.end)}</span>
          {seg.speed && seg.speed !== 1.0 && (
            <span className="seg-speed-badge" style={{ color: seg.speed > 1 ? '#d3869b' : '#8ec07c' }}>
              {seg.speed.toFixed(2)}x
            </span>
          )}
        </span>
        {SyncIcon && (
          <span
            className="seg-sync-badge"
            style={{ color: syncColor }}
            title={`Generated audio is ${Math.round(seg.sync_ratio * 100)}% the duration of original`}
          >
            <SyncIcon size={8} /> Sync: {Math.round(seg.sync_ratio * 100)}%
          </span>
        )}
        {seg.rate_ratio != null && Math.abs(seg.rate_ratio - 1.0) > 0.03 && (
          <span
            className="seg-rate-badge"
            style={{ color: seg.rate_ratio > 1.15 ? '#fb4934' : seg.rate_ratio < 0.85 ? '#83a598' : '#a89984' }}
            title={`Speech-rate fit: ${seg.rate_ratio.toFixed(2)}× relative to slot${seg.rate_error ? ` (${seg.rate_error})` : ''}`}
          >
            📖 {seg.rate_ratio.toFixed(2)}×
          </span>
        )}
      </span>

      <input
        className="input-base seg-speaker-input"
        value={seg.speaker_id || ''}
        onChange={(e) => onEditField(seg.id, 'speaker_id', e.target.value)}
        onClick={(e) => e.stopPropagation()}
        disabled={disabled}
        list={speakerOptions.length ? speakerListId : undefined}
        placeholder={speakerOptions.length ? 'Pick…' : ''}
        title={speakerOptions.length
          ? 'Speaker — pick from detected, or type a custom name'
          : 'Speaker — type a name (no diarization clones detected)'}
      />
      {speakerOptions.length > 0 && (
        <datalist id={speakerListId}>
          {speakerOptions.map(spk => <option key={spk} value={spk} />)}
        </datalist>
      )}

      <span className="seg-text-col">
        <input
          ref={textInputRef}
          className="input-base segment-input"
          value={seg.text}
          onChange={(e) => onEditField(seg.id, 'text', e.target.value)}
          onKeyDown={handleTextKeyDown}
          onKeyUp={captureCursor}
          onSelect={captureCursor}
          onClick={(e) => { e.stopPropagation(); captureCursor(e); }}
          disabled={disabled}
          title={seg.translate_error
            ? `Translation error: ${seg.translate_error}`
            : overBudget
              ? `Text is ${Math.round((seg.text.length / seg.text_original.length) * 100)}% of original — consider higher speed or shorter phrasing`
              : 'Ctrl+D to split at cursor · Ctrl+M to merge with next'}
          style={
            overBudget ? { borderColor: 'rgba(250,189,47,0.6)', background: 'rgba(250,189,47,0.06)' }
            : seg.translate_error ? { borderColor: 'rgba(251,73,52,0.5)' }
            : undefined
          }
        />
        {seg.text_original && seg.text_original !== seg.text && (
          <span className="seg-orig-row">
            <span className="seg-orig-label">orig</span>
            <span className="seg-orig-text" title={seg.text_original}>
              {seg.text_original}
            </span>
            {overBudget && (
              <span className="seg-budget-warn">
                {Math.round((seg.text.length / seg.text_original.length) * 100)}%
              </span>
            )}
            <button
              onClick={() => onRestore(seg.id)}
              disabled={disabled}
              title="Restore original text"
              className="seg-restore-btn"
            >
              ↺
            </button>
          </span>
        )}
      </span>

      <select
        className="input-base seg-lang-select"
        value={seg.target_lang || ''}
        disabled={disabled}
        onChange={(e) => onEditField(seg.id, 'target_lang', e.target.value)}
      >
        <option value="">(Def)</option>
        {LANG_CODES.map(lc => (
          <option key={lc.code} value={lc.code}>{lc.code.toUpperCase()}</option>
        ))}
      </select>

      <select
        className="input-base seg-profile-select"
        value={seg.profile_id || ''}
        disabled={disabled}
        onChange={(e) => onEditField(seg.id, 'profile_id', e.target.value)}
      >
        <option value="">Default</option>
        {speakerClones && Object.keys(speakerClones).length > 0 && (
          <optgroup label="From Video">
            {Object.keys(speakerClones).map(spk => {
              const autoId = `auto:${(spk || '').toLowerCase().replace(/\s+/g, '_')}`;
              return <option key={autoId} value={autoId}>🎤 {spk}</option>;
            })}
          </optgroup>
        )}
        {profiles.length > 0 && (
          <optgroup label="Clone Profiles">
            {profiles.map(p => <option key={p.id} value={p.id}>{p.name}</option>)}
          </optgroup>
        )}
        {PRESETS.length > 0 && (
          <optgroup label="Design Presets">
            {PRESETS.map(p => <option key={p.id} value={`preset:${p.id}`}>{p.name}</option>)}
          </optgroup>
        )}
      </select>

      <input
        type="range"
        min="0" max="200"
        value={Math.round((seg.gain ?? 1.0) * 100)}
        title={`${Math.round((seg.gain ?? 1.0) * 100)}%`}
        disabled={disabled}
        onChange={(e) => onEditField(seg.id, 'gain', Number(e.target.value) / 100)}
        className="seg-gain-slider"
        style={{ accentColor: (seg.gain ?? 1.0) > 1.2 ? '#fb4934' : (seg.gain ?? 1.0) < 0.5 ? '#83a598' : '#a89984' }}
      />

      <div className="seg-actions">
        <button
          className="segment-play"
          disabled={disabled}
          title="Live Preview"
          onClick={(e) => onPreview(seg, e)}
        >
          {previewLoading ? <Loader className="spinner" size={9} /> : <Headphones size={9} />}
        </button>
        <Menu
          placement="bottom-end"
          disabled={disabled}
          items={[
            {
              id: 'direct',
              label: seg.direction ? 'Edit direction…' : 'Set direction…',
              icon: Sparkles,
              onSelect: () => onDirect?.(seg),
            },
            'separator',
            {
              id: 'split',
              label: 'Split at cursor',
              icon: Scissors,
              shortcut: '⌘D',
              onSelect: () => {
                // Prefer the live cursor if the text input still has focus,
                // otherwise fall back to the last remembered position, then
                // to a sentence-boundary heuristic. Splitting at the literal
                // midpoint (the old behaviour) felt random to users.
                let pos = textInputRef.current?.selectionStart;
                if (pos == null || pos <= 0 || pos >= seg.text.length) {
                  pos = lastCursorRef.current;
                }
                if (pos == null || pos <= 0 || pos >= seg.text.length) {
                  pos = bestSplitPoint(seg.text);
                }
                onSplit(seg.id, pos);
              },
            },
            {
              id: 'merge',
              label: 'Merge with next',
              icon: Merge,
              shortcut: '⌘M',
              disabled: !canMerge,
              onSelect: () => onMerge(seg.id),
            },
          ]}
        >
          <button
            className={`segment-play ${seg.direction ? 'has-direction' : ''}`}
            disabled={disabled}
            title={seg.direction ? `Direction: ${seg.direction}` : 'More actions'}
          >
            {seg.direction ? <Sparkles size={9} /> : <MoreHorizontal size={9} />}
          </button>
        </Menu>
        <button
          className="segment-del"
          disabled={disabled}
          onClick={() => onDelete(seg.id)}
        >
          <Trash2 size={9} />
        </button>
      </div>
    </div>
  );
}

export default memo(DubSegmentRow, (prev, next) => (
  prev.seg === next.seg &&
  prev.disabled === next.disabled &&
  prev.isActive === next.isActive &&
  prev.isDone === next.isDone &&
  prev.previewLoading === next.previewLoading &&
  prev.onDirect === next.onDirect &&
  prev.onSeek === next.onSeek &&
  prev.selected === next.selected &&
  prev.canMerge === next.canMerge &&
  prev.profiles === next.profiles &&
  prev.speakerClones === next.speakerClones &&
  prev.idx === next.idx
));
