// ChangePanel.jsx — the §8.8 change-card panel.
//
// Sits below the chat in contribute mode. Pending cards stack on top
// of resolved stubs. Each AI card carries accept / edit-before-accept /
// decline per §8.9; each manual card carries the live status line per
// §8.11. Stale cards surface the §8.11 warning + Re-ask path. Clicking
// a card's "↑ from this message" affordance scrolls the chat back to
// the originating message.

import { useState, useEffect, useRef } from 'react'

const PREVIEW_LENGTH = 220

// Word-level LCS over whitespace-preserving tokens. Used by both the
// AI change-card InlineDiff (original→proposed) and Phase 5's
// manual-pending-card per-paragraph diff (baseline→current).
export function diffWords(original, proposed) {
  const a = (original || '').split(/(\s+)/)
  const b = (proposed || '').split(/(\s+)/)
  const m = a.length, n = b.length
  const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0))
  for (let i = 1; i <= m; i++)
    for (let j = 1; j <= n; j++)
      dp[i][j] = a[i-1] === b[j-1]
        ? dp[i-1][j-1] + 1
        : Math.max(dp[i-1][j], dp[i][j-1])
  const tokens = []
  let i = m, j = n
  while (i > 0 || j > 0) {
    if (i > 0 && j > 0 && a[i-1] === b[j-1]) {
      tokens.unshift({ text: a[i-1], type: 'same' }); i--; j--
    } else if (j > 0 && (i === 0 || dp[i][j-1] >= dp[i-1][j])) {
      tokens.unshift({ text: b[j-1], type: 'add' }); j--
    } else {
      tokens.unshift({ text: a[i-1], type: 'remove' }); i--
    }
  }
  return tokens
}

function InlineDiff({ original, proposed }) {
  const [expanded, setExpanded] = useState(false)
  const tokens = diffWords(original, proposed)
  const fullText = tokens.map(t => t.text).join('')
  const needsTruncation = fullText.length > PREVIEW_LENGTH
  let shown = tokens
  if (needsTruncation && !expanded) {
    let count = 0
    const cutoff = tokens.findIndex(t => { count += t.text.length; return count > PREVIEW_LENGTH })
    if (cutoff !== -1) shown = tokens.slice(0, cutoff)
  }
  return (
    <div className="inline-diff">
      {shown.map((token, idx) =>
        token.type === 'same' ? <span key={idx}>{token.text}</span> :
        token.type === 'add'  ? <span key={idx} className="diff-word-add">{token.text}</span> :
                                <span key={idx} className="diff-word-remove">{token.text}</span>
      )}
      {needsTruncation && !expanded && <span className="text-fade">…</span>}
      {needsTruncation && (
        <button className="expand-toggle" onClick={() => setExpanded(e => !e)}>
          {expanded ? '↑ Show less' : '↓ Show more'}
        </button>
      )}
    </div>
  )
}

export default function ChangePanel({
  changes,
  onAccept,
  onDecline,
  onReask,
  onScrollToMessage,
  focusedChangeId,
  manualPendingStatus,  // {paragraphCount, savingIn, onSaveNow} or null
}) {
  const pending = changes.filter(c => c.state === 'pending')
  const resolved = changes.filter(c => c.state !== 'pending')

  return (
    <div className="change-panel">
      <div className="change-panel-header">
        Changes
        {pending.length > 0 && <span className="badge">{pending.length}</span>}
      </div>
      <div className="change-list">
        {manualPendingStatus && (
          <ManualPendingCard
            paragraphCount={manualPendingStatus.paragraphCount}
            diffs={manualPendingStatus.diffs}
            savingIn={manualPendingStatus.savingIn}
            onSaveNow={manualPendingStatus.onSaveNow}
          />
        )}
        {pending.length > 0 && (
          <div className="change-group-label">Pending</div>
        )}
        {pending.map(c => (
          <ChangeItem
            key={c.id}
            change={c}
            focused={focusedChangeId === c.id}
            onAccept={onAccept}
            onDecline={onDecline}
            onReask={onReask}
            onScrollToMessage={onScrollToMessage}
          />
        ))}
        {resolved.length > 0 && (
          <div className="change-group-label muted">Resolved</div>
        )}
        {resolved.map(c => (
          <ResolvedStub key={c.id} change={c} onScrollToMessage={onScrollToMessage} />
        ))}
      </div>
    </div>
  )
}

function ManualPendingCard({ paragraphCount, diffs, savingIn, onSaveNow }) {
  return (
    <div className="change-item type-manual state-pending">
      <div className="change-meta">
        <span className="change-author">You · manual edit</span>
        <span className="change-state-badge pending">unsaved</span>
      </div>
      <div className="change-label">
        {paragraphCount} paragraph{paragraphCount === 1 ? '' : 's'} edited directly
      </div>
      {diffs && diffs.length > 0 && (
        <div className="change-manual-diff">
          {diffs.map(d => (
            <div key={d.baselineIndex} className="change-manual-diff-block">
              {d.tokens.map((t, idx) =>
                t.type === 'same'   ? <span key={idx}>{t.text}</span> :
                t.type === 'add'    ? <span key={idx} className="manual-diff-insert">{t.text}</span> :
                                      <span key={idx} className="manual-diff-delete">{t.text}</span>
              )}
            </div>
          ))}
        </div>
      )}
      <div className="change-manual-status">
        unsaved · auto-save in {savingIn}
        <button type="button" className="btn-save-now" onClick={onSaveNow}>Save now</button>
      </div>
    </div>
  )
}

function ChangeItem({ change, focused, onAccept, onDecline, onReask, onScrollToMessage }) {
  const [editing, setEditing] = useState(false)
  const [edited, setEdited] = useState(change.proposed || '')
  const itemRef = useRef(null)
  const isStale = !!change.stale_since

  useEffect(() => {
    if (focused && itemRef.current) {
      itemRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
    }
  }, [focused])

  const handleStartEdit = () => { setEdited(change.proposed || ''); setEditing(true) }
  const handleAcceptEdited = () => {
    onAccept({ change, proposed: edited, wasEdited: true })
    setEditing(false)
  }
  const handleAcceptStraight = () => {
    onAccept({ change, proposed: change.proposed, wasEdited: false })
  }

  return (
    <div
      ref={itemRef}
      className={`change-item state-${change.state} type-${change.kind === 'ai' ? 'claude' : 'manual'}${focused ? ' focused' : ''}${isStale ? ' stale' : ''}`}
    >
      <div className="change-meta">
        <span className="change-author">{change.kind === 'ai' ? 'AI' : 'You'}</span>
        <span className={`change-state-badge ${change.state}`}>
          {isStale ? 'stale' : change.state}
        </span>
      </div>
      {change.reason && (
        <div className="change-label">{change.reason}</div>
      )}
      {isStale && (
        <div className="change-stale-banner">
          The original text has changed since this was proposed.
        </div>
      )}
      {change.kind === 'ai' && (
        <div className="change-diff">
          {editing ? (
            <textarea
              className="diff-edit-textarea"
              value={edited}
              onChange={e => setEdited(e.target.value)}
              rows={Math.min(12, Math.max(3, edited.split('\n').length + 1))}
              autoFocus
            />
          ) : (
            <InlineDiff original={change.original} proposed={change.proposed} />
          )}
        </div>
      )}
      {change.source_message_id && (
        <button
          type="button"
          className="change-source-link"
          onClick={() => onScrollToMessage?.(change.source_message_id)}
        >↑ from this message</button>
      )}
      <div className="change-actions">
        {editing ? (
          <>
            <button className="btn-accept" onClick={handleAcceptEdited}>Accept edit</button>
            <button className="btn-edit" onClick={() => setEditing(false)}>Cancel</button>
          </>
        ) : (
          <>
            <button className="btn-accept" onClick={handleAcceptStraight}>
              {isStale ? 'Apply anyway…' : 'Accept'}
            </button>
            <button className="btn-edit" onClick={handleStartEdit}>Edit</button>
            <button className="btn-decline" onClick={() => onDecline(change.id)}>Decline</button>
            {isStale && (
              <button className="btn-reask" onClick={() => onReask?.(change.id)}>Re-ask</button>
            )}
          </>
        )}
      </div>
    </div>
  )
}

function ResolvedStub({ change, onScrollToMessage }) {
  const short = (change.reason || '').slice(0, 80)
  return (
    <div className={`change-stub state-${change.state}`}>
      <span className="stub-author">{change.kind === 'ai' ? 'AI' : 'You'}</span>
      <span className={`stub-badge ${change.state}`}>{change.state}</span>
      <span className="stub-reason">{short || (change.kind === 'manual' ? 'manual edit' : 'change')}</span>
      {change.source_message_id && (
        <button
          type="button"
          className="stub-source-link"
          onClick={() => onScrollToMessage?.(change.source_message_id)}
        >↑</button>
      )}
    </div>
  )
}
