// GraduateDialog.jsx — the §13.2 Graduate dialog and the §13.3 step stack.
//
// Renders three editable fields (integer ID, repo name, initial owners)
// with debounced server-side validation per §13.2 and a precondition
// popover backed by /blocking-prs for the §9.8 open-body-edit-PR gate.
//
// On confirm, opens the §13.3 SSE stream and renders the five named
// steps with per-step states. On failure, the rollback step's events
// append to the stack and a "What happened" panel renders below until
// the admin dismisses it.

import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import {
  graduateCheck,
  listBlockingPRs,
  openGraduationProgress,
  startGraduation,
} from '../api'

const CHECK_DEBOUNCE_MS = 250

const STEP_KEY_ORDER = ['create_repo', 'seed_files', 'open_pr', 'merge_pr', 'refresh_cache']

export default function GraduateDialog({ slug, entry, onClose, onCompleted }) {
  // Suggest defaults from the catalog.
  const suggestedId = useMemo(() => suggestNextRfcId(entry?.allKnownIds || []), [entry])
  const [rfcId, setRfcId] = useState(suggestedId)
  const [repoName, setRepoName] = useState(`rfc-${stripPrefix(suggestedId)}-${slug}`)
  const [owners, setOwners] = useState(entry?.owners?.length ? entry.owners : [])
  const [newOwner, setNewOwner] = useState('')

  const [checkResult, setCheckResult] = useState(null)
  const [blockingPRs, setBlockingPRs] = useState([])
  const [precondPopover, setPrecondPopover] = useState(false)
  const [phase, setPhase] = useState('idle')  // idle | running | done | rolled_back | error
  const [streamState, setStreamState] = useState(null)
  const [submitError, setSubmitError] = useState(null)

  const esRef = useRef(null)

  // Initial blocking-PRs probe + ongoing /check polling.
  useEffect(() => {
    listBlockingPRs(slug).then(({ items }) => setBlockingPRs(items || [])).catch(() => {})
  }, [slug])

  useEffect(() => {
    const t = setTimeout(() => {
      graduateCheck(slug, { id: rfcId, repo: repoName })
        .then(setCheckResult)
        .catch(() => {})
    }, CHECK_DEBOUNCE_MS)
    return () => clearTimeout(t)
  }, [slug, rfcId, repoName])

  useEffect(() => () => { esRef.current?.close() }, [])

  const idError = checkResult?.id?.error || null
  const repoError = checkResult?.repo?.error || null
  const ownersOk = owners.length > 0
  const ownersError = ownersOk ? null : 'Add at least one initial owner'
  const blockingError = blockingPRs.length > 0
    ? `${blockingPRs.length} open body-edit PR${blockingPRs.length === 1 ? '' : 's'} blocking graduation`
    : null

  // First-blocker tooltip text per §13.2.
  const firstBlocker = idError || repoError || ownersError || blockingError
  const canSubmit = !firstBlocker && phase === 'idle' && checkResult?.id?.ok && checkResult?.repo?.ok

  const handleAddOwner = useCallback(() => {
    const v = newOwner.trim().toLowerCase()
    if (!v || owners.includes(v)) return
    setOwners(prev => [...prev, v])
    setNewOwner('')
  }, [newOwner, owners])

  const handleRemoveOwner = useCallback((login) => {
    setOwners(prev => prev.filter(o => o !== login))
  }, [])

  const handleConfirm = useCallback(async () => {
    setSubmitError(null)
    setPhase('running')
    try {
      await startGraduation(slug, { rfcId, repoName, owners })
    } catch (err) {
      setPhase('idle')
      setSubmitError(err.message)
      return
    }
    esRef.current = openGraduationProgress(slug, {
      onUpdate: (payload) => {
        setStreamState(payload)
        if (payload?.finished) {
          if (payload.succeeded) {
            setPhase('done')
            // Short hold per §13.3, then dismiss.
            setTimeout(() => onCompleted?.(payload), 1500)
          } else {
            setPhase('rolled_back')
          }
        }
      },
      onError: () => {
        setSubmitError('Lost connection to the graduation stream — refresh to see current state.')
        setPhase('error')
      },
    })
  }, [slug, rfcId, repoName, owners, onCompleted])

  // ----- Render -----

  const showStack = phase !== 'idle' && (streamState?.steps?.length || 0) > 0

  return (
    <div className="modal-overlay" onClick={(e) => { if (e.target === e.currentTarget && phase === 'idle') onClose?.() }}>
      <div className="modal modal-wide">
        <div className="modal-header">
          <h2>Graduate `{slug}` to active</h2>
          {phase === 'idle' && <button className="modal-close" onClick={onClose}>×</button>}
        </div>

        {!showStack && (
          <div className="modal-body">
            <p className="modal-intro">
              §13: graduate the super-draft to its own repo. The meta-repo entry
              becomes frontmatter-only; the canonical body moves to `RFC.md` in
              the new repo. The sequence runs as five transactional steps with
              rollback per §13.3.
            </p>

            <div className="form-row">
              <label>Integer ID</label>
              <input
                type="text"
                value={rfcId}
                onChange={(e) => setRfcId(e.target.value.trim())}
                placeholder="RFC-NNNN"
                disabled={phase !== 'idle'}
              />
              <p className="field-help">Pre-filled as the next free integer; editable to reserve gaps.</p>
              {idError && <p className="field-error">{idError}</p>}
            </div>

            <div className="form-row">
              <label>Repo name</label>
              <input
                type="text"
                value={repoName}
                onChange={(e) => setRepoName(e.target.value.trim())}
                placeholder="rfc-NNNN-slug"
                disabled={phase !== 'idle'}
              />
              <p className="field-help">Becomes `&lt;org&gt;/{repoName || 'rfc-…'}` on Gitea.</p>
              {repoError && <p className="field-error">{repoError}</p>}
            </div>

            <div className="form-row">
              <label>Initial owners</label>
              <div className="owner-list">
                {owners.length === 0 && <span className="owner-empty">No owners yet — add at least one.</span>}
                {owners.map(o => (
                  <span key={o} className="owner-chip">
                    {o}
                    <button
                      type="button"
                      className="owner-chip-x"
                      onClick={() => handleRemoveOwner(o)}
                      disabled={phase !== 'idle'}
                      aria-label={`Remove ${o}`}
                    >×</button>
                  </span>
                ))}
              </div>
              <div className="owner-picker">
                <input
                  type="text"
                  value={newOwner}
                  onChange={(e) => setNewOwner(e.target.value)}
                  placeholder="Gitea login"
                  disabled={phase !== 'idle'}
                  onKeyDown={(e) => { if (e.key === 'Enter') handleAddOwner() }}
                />
                <button
                  type="button"
                  className="btn-secondary"
                  onClick={handleAddOwner}
                  disabled={phase !== 'idle' || !newOwner.trim()}
                >Add</button>
              </div>
              {ownersError && <p className="field-error">{ownersError}</p>}
            </div>

            {blockingPRs.length > 0 && (
              <div className="precondition-block">
                <button
                  type="button"
                  className="precondition-toggle"
                  onClick={() => setPrecondPopover(p => !p)}
                >
                  {blockingPRs.length} open body-edit PR{blockingPRs.length === 1 ? '' : 's'} blocking graduation
                  &nbsp;{precondPopover ? '▾' : '▸'}
                </button>
                {precondPopover && (
                  <div className="precondition-popover">
                    {blockingPRs.map(pr => (
                      <div key={pr.pr_number} className="precondition-row">
                        <div className="precondition-row-main">
                          <strong>PR #{pr.pr_number}</strong> — {pr.title || '(no title)'}
                          <div className="precondition-row-meta">
                            {pr.author ? `by @${pr.author}` : ''}
                            {pr.last_activity_at ? ` · ${pr.last_activity_at.slice(0, 10)}` : ''}
                          </div>
                        </div>
                        <div className="precondition-row-actions">
                          <a
                            className="btn-link"
                            href={`/rfc/${slug}/pr/${pr.pr_number}`}
                            target="_blank"
                            rel="noreferrer"
                          >Open ↗</a>
                        </div>
                      </div>
                    ))}
                    <p className="precondition-help">
                      §9.8: open body-edit PRs would attempt to re-introduce a
                      body to a frontmatter-only entry after step 3. Resolve
                      them (merge or withdraw) and re-open this dialog.
                    </p>
                  </div>
                )}
              </div>
            )}
          </div>
        )}

        {showStack && (
          <div className="modal-body">
            <StepStack
              steps={streamState?.steps || []}
              rollbackSteps={streamState?.rollback_steps || []}
            />
            {phase === 'rolled_back' && (
              <div className="what-happened">
                <h3>What happened</h3>
                <p>
                  The graduation could not complete. The app rolled back the
                  steps that had already run; nothing was left half-applied on
                  Gitea. Error: <code>{streamState?.error || 'unknown'}</code>.
                </p>
                <p>
                  Read the failure detail next to the red step above. Resolve
                  the underlying cause (a repo-name collision, a network flake,
                  a concurrent PR landing on `rfcs/{slug}.md`) and try again.
                </p>
              </div>
            )}
            {phase === 'done' && (
              <div className="graduation-complete">
                <h3>Graduation complete</h3>
                <p>
                  `{slug}` is now active as <strong>{streamState?.rfc_id}</strong>{' '}
                  at <code>{streamState?.repo_full}</code>. The catalog and the
                  RFC view reflect the new state.
                </p>
              </div>
            )}
          </div>
        )}

        <div className="modal-actions">
          {phase === 'idle' && (
            <>
              <button className="btn-secondary" onClick={onClose}>Cancel</button>
              <button
                className="btn-primary"
                onClick={handleConfirm}
                disabled={!canSubmit}
                title={canSubmit ? '' : firstBlocker || ''}
              >
                Graduate to RFC repo
              </button>
            </>
          )}
          {phase === 'running' && (
            <span className="modal-progress-note">Running graduation sequence…</span>
          )}
          {(phase === 'rolled_back' || phase === 'error') && (
            <button className="btn-secondary" onClick={onClose}>Close</button>
          )}
          {phase === 'done' && (
            <button className="btn-primary" onClick={() => onCompleted?.(streamState)}>
              View the new RFC
            </button>
          )}
        </div>
        {submitError && phase !== 'rolled_back' && (
          <div className="modal-error">Error: {submitError}</div>
        )}
      </div>
    </div>
  )
}


function StepStack({ steps, rollbackSteps }) {
  return (
    <div className="step-stack">
      {steps.map(s => <StepRow key={s.key} step={s} />)}
      {rollbackSteps.length > 0 && (
        <div className="rollback-divider">Rollback</div>
      )}
      {rollbackSteps.map(s => <StepRow key={s.key} step={s} />)}
    </div>
  )
}


function StepRow({ step }) {
  return (
    <div className={`step-row step-${step.status}`}>
      <span className={`step-marker step-marker-${step.status}`} />
      <div className="step-text">
        <div className="step-label">{step.label}</div>
        {step.detail && <div className="step-detail">{step.detail}</div>}
      </div>
      <div className={`step-status-pill pill-${step.status}`}>{labelFor(step.status)}</div>
    </div>
  )
}


function labelFor(status) {
  switch (status) {
    case 'pending': return 'pending'
    case 'running': return 'running'
    case 'done': return 'done'
    case 'failed': return 'failed'
    case 'not-reached': return 'not reached'
    default: return status
  }
}


function suggestNextRfcId(existing) {
  const used = new Set()
  for (const id of existing) {
    const m = /^RFC-(\d+)$/.exec(id || '')
    if (m) used.add(Number(m[1]))
  }
  const next = used.size === 0 ? 1 : (Math.max(...used) + 1)
  return `RFC-${String(next).padStart(4, '0')}`
}


function stripPrefix(rfcId) {
  return rfcId?.startsWith('RFC-') ? rfcId.slice(4) : rfcId
}
