import { useEffect, useRef, useState } from 'react';
import { formatBytes, formatDuration } from '../app/application-state';
import type { ExportReport, ExportResult } from '../export';
import type { EngineState } from '../ffmpeg/ffmpeg.types';
import type { MediaJob } from '../jobs';
import {
  assessGeneratedResultPreview,
  formatTimecode,
  type MediaProbe,
} from '../media';
import { Icon } from './Icon';

export interface ResultVerification {
  readonly probe?: MediaProbe;
  readonly warning?: string;
}

export interface JobPanelProps {
  engineState: EngineState;
  jobs: readonly MediaJob[];
  results: readonly ExportResult[];
  selectedResultId?: string;
  verifications: Readonly<Record<string, ResultVerification>>;
  report?: ExportReport;
  logs: readonly string[];
  error?: string;
  queuedCount?: number;
  onCancel: () => void;
  onCancelJob: (job: MediaJob) => void;
  onRetryJob: (job: MediaJob) => void;
  onClearJobHistory: () => void;
  onSave: (result: ExportResult) => void;
  onSaveZip: (results: readonly ExportResult[]) => void;
  onSaveReport: () => void;
  onPreview: (result: ExportResult) => void;
  onRemove: (result: ExportResult) => void;
  onClearResults: () => void;
}

export function JobPanel({
  engineState,
  jobs,
  results,
  selectedResultId,
  verifications,
  report,
  logs,
  error,
  queuedCount = 0,
  onCancel,
  onCancelJob,
  onRetryJob,
  onClearJobHistory,
  onSave,
  onSaveZip,
  onSaveReport,
  onPreview,
  onRemove,
  onClearResults,
}: JobPanelProps) {
  const [selectedResultIds, setSelectedResultIds] = useState<
    ReadonlySet<string>
  >(() => new Set());
  const knownResultIds = useRef<ReadonlySet<string>>(new Set());
  useEffect(() => {
    setSelectedResultIds((current) => {
      const next = new Set(
        results
          .filter(
            (result) =>
              current.has(result.id) || !knownResultIds.current.has(result.id)
          )
          .map((result) => result.id)
      );
      knownResultIds.current = new Set(results.map((result) => result.id));
      return next;
    });
  }, [results]);
  const running = engineState.status === 'running';
  if (
    !running &&
    queuedCount === 0 &&
    jobs.length === 0 &&
    results.length === 0 &&
    !error &&
    logs.length === 0
  ) {
    return null;
  }

  return (
    <section className="panel job-panel" aria-labelledby="jobs-title">
      <header className="panel__header">
        <div>
          <p className="eyebrow">Local FFmpeg queue</p>
          <h2 id="jobs-title">
            {running
              ? 'Processing one operation'
              : results.length === 1
                ? 'Current output'
                : 'Current outputs'}
          </h2>
        </div>
        {queuedCount > 0 ? (
          <span className="job-panel__queue-count" role="status">
            {queuedCount} waiting
          </span>
        ) : null}
        {running ? (
          <button
            className="button button--danger"
            type="button"
            onClick={onCancel}
          >
            <Icon name="cancel" />
            Cancel & recover
          </button>
        ) : null}
      </header>
      <div className="job-panel__body" aria-live="polite">
        {queuedCount > 0 ? (
          <p className="job-queue-note">
            Additional operations are retained in memory and start one at a
            time. Closing this page discards waiting source handles.
          </p>
        ) : null}
        {running ? (
          <div className="job-progress">
            <div>
              <strong>{engineState.operation}</strong>
              <span>
                FFmpeg progress is an estimate; completion requires exit code
                zero.
              </span>
            </div>
            <span>
              {engineState.progress === undefined
                ? 'Working…'
                : `${Math.round(engineState.progress * 100)}%`}
              {engineState.elapsedSeconds !== undefined
                ? ` · ${Math.round(engineState.elapsedSeconds)} s`
                : ''}
              {engineState.speed !== undefined
                ? ` · ${engineState.speed.toFixed(2)}×`
                : ''}
            </span>
            <progress value={engineState.progress} max="1" />
          </div>
        ) : null}
        {jobs.length > 0 ? (
          <section className="job-history" aria-labelledby="job-history-title">
            <header>
              <h3 id="job-history-title">Job history</h3>
              {jobs.some((job) => isTerminalJob(job)) ? (
                <button
                  className="button button--quiet"
                  type="button"
                  onClick={onClearJobHistory}
                >
                  Clear finished
                </button>
              ) : null}
            </header>
            <div className="job-history__list">
              {jobs.map((job) => (
                <article className="job-record" key={job.id}>
                  <div className="job-record__summary">
                    <div>
                      <strong>{job.operation}</strong>
                      <span>
                        {job.status.replace('-', ' ')}
                        {job.startedAt
                          ? ` · ${formatJobTime(job.startedAt)}`
                          : ''}
                      </span>
                    </div>
                    <b>{Math.round(job.progress * 100)}%</b>
                    {job.status === 'queued' ? (
                      <button
                        className="button button--quiet"
                        type="button"
                        onClick={() => onCancelJob(job)}
                      >
                        Cancel waiting
                      </button>
                    ) : null}
                    {job.status === 'failed' || job.status === 'cancelled' ? (
                      <button
                        className="button button--quiet"
                        type="button"
                        onClick={() => onRetryJob(job)}
                      >
                        Retry
                      </button>
                    ) : null}
                  </div>
                  <progress value={job.progress} max="1" />
                  {job.error ? (
                    <small className="job-record__error">
                      {job.error.message}
                    </small>
                  ) : null}
                  {job.steps.length > 1 ? (
                    <details>
                      <summary>{job.steps.length} local steps</summary>
                      <ol>
                        {job.steps.map((step) => (
                          <li key={step.id}>
                            <span>{step.name}</span>
                            <span>
                              {step.status} · {Math.round(step.progress * 100)}%
                            </span>
                          </li>
                        ))}
                      </ol>
                    </details>
                  ) : null}
                </article>
              ))}
            </div>
          </section>
        ) : null}
        {error ? (
          <div className="notice notice--error" role="alert">
            <Icon name="warning" />
            <div>
              <strong>Output failed</strong>
              <span>{error}</span>
            </div>
          </div>
        ) : null}
        {results.length > 0 ? (
          <>
            {results.length > 1 ? (
              <label className="result-select-all">
                <input
                  type="checkbox"
                  checked={selectedResultIds.size === results.length}
                  onChange={(event) =>
                    setSelectedResultIds(
                      event.currentTarget.checked
                        ? new Set(results.map((result) => result.id))
                        : new Set()
                    )
                  }
                />
                Select all generated results for ZIP
              </label>
            ) : null}
            <div className="result-list">
              {results.map((result) => {
                const verification = verifications[result.id];
                const preview = generatedResultPreview(
                  result,
                  verification?.probe
                );
                return (
                  <article
                    className={`result-card${selectedResultId === result.id ? ' is-selected' : ''}`}
                    key={result.id}
                  >
                    {results.length > 1 ? (
                      <input
                        className="result-card__select"
                        type="checkbox"
                        aria-label={`Select ${result.fileName} for ZIP`}
                        checked={selectedResultIds.has(result.id)}
                        onChange={(event) => {
                          const checked = event.currentTarget.checked;
                          setSelectedResultIds((current) => {
                            const next = new Set(current);
                            if (checked) next.add(result.id);
                            else next.delete(result.id);
                            return next;
                          });
                        }}
                      />
                    ) : null}
                    <span className="result-card__icon">
                      <Icon name="check" />
                    </span>
                    <div>
                      <strong>{result.fileName}</strong>
                      <span>
                        {formatBytes(result.size)} ·{' '}
                        {verification?.warning
                          ? 'created · verification warning'
                          : verification?.probe
                            ? 'created · verified'
                            : 'completed locally'}
                        {verification?.probe?.durationSeconds !== undefined
                          ? ` · ${formatDuration(verification.probe.durationSeconds)}`
                          : ''}
                        {verification?.probe
                          ? ` · ${verification.probe.streams.length} verified stream${verification.probe.streams.length === 1 ? '' : 's'}`
                          : ''}
                      </span>
                      {result.timeRange ? (
                        <small className="result-card__range">
                          Segment range{' '}
                          {formatTimecode(result.timeRange.startSeconds)}–
                          {formatTimecode(result.timeRange.endSeconds)} ·{' '}
                          {formatTimecode(
                            result.timeRange.endSeconds -
                              result.timeRange.startSeconds
                          )}{' '}
                          duration
                        </small>
                      ) : null}
                      {verification?.warning ? (
                        <small className="result-card__warning">
                          {verification.warning}
                        </small>
                      ) : null}
                      {!preview.previewable && preview.kind !== 'other' ? (
                        <small className="result-card__warning">
                          Download only: {preview.reason}
                        </small>
                      ) : null}
                      {verification?.probe ? (
                        <details className="result-verification">
                          <summary>Round-trip probe details</summary>
                          <dl>
                            <div>
                              <dt>Container tags</dt>
                              <dd>
                                {formatTagSummary(verification.probe.tags)}
                              </dd>
                            </div>
                            <div>
                              <dt>Chapters</dt>
                              <dd>
                                {verification.probe.chapters.length === 0
                                  ? 'None'
                                  : verification.probe.chapters
                                      .map(
                                        (chapter) =>
                                          chapter.title ??
                                          `Chapter ${chapter.id + 1}`
                                      )
                                      .join(', ')}
                              </dd>
                            </div>
                            {verification.probe.streams.map((stream) => (
                              <div key={stream.index}>
                                <dt>
                                  {stream.type} #{stream.index}
                                </dt>
                                <dd>
                                  {stream.codecName ?? 'unknown codec'}
                                  {stream.language
                                    ? ` · ${stream.language}`
                                    : ''}
                                  {stream.title ? ` · ${stream.title}` : ''}
                                  {` · ${formatTagSummary(stream.tags)}`}
                                  {` · disposition: ${enabledDispositions(
                                    stream.disposition
                                  )}`}
                                </dd>
                              </div>
                            ))}
                          </dl>
                        </details>
                      ) : null}
                    </div>
                    {preview.previewable ? (
                      <button
                        className="button button--quiet"
                        type="button"
                        aria-pressed={selectedResultId === result.id}
                        onClick={() => onPreview(result)}
                      >
                        Preview
                      </button>
                    ) : null}
                    <button
                      className="button button--primary"
                      type="button"
                      aria-label={
                        results.length === 1
                          ? 'Save result'
                          : `Save ${result.fileName}`
                      }
                      onClick={() => onSave(result)}
                    >
                      <Icon name="download" />
                      {results.length === 1 ? 'Save result' : 'Save'}
                    </button>
                    <button
                      className="icon-action"
                      type="button"
                      aria-label={`Remove ${result.fileName}`}
                      onClick={() => onRemove(result)}
                    >
                      <Icon name="trash" />
                    </button>
                  </article>
                );
              })}
            </div>
            <div className="result-actions">
              {results.length > 1 ? (
                <button
                  className="button button--secondary"
                  type="button"
                  disabled={selectedResultIds.size === 0}
                  onClick={() =>
                    onSaveZip(
                      results.filter((result) =>
                        selectedResultIds.has(result.id)
                      )
                    )
                  }
                >
                  <Icon name="download" />
                  Download selected as ZIP ({selectedResultIds.size})
                </button>
              ) : null}
              {report ? (
                <button
                  className="button button--quiet"
                  type="button"
                  onClick={onSaveReport}
                >
                  Export validation report
                </button>
              ) : null}
              <button
                className="button button--quiet"
                type="button"
                onClick={onClearResults}
              >
                Clear generated results
              </button>
            </div>
          </>
        ) : null}
        {logs.length > 0 ? (
          <details className="diagnostics">
            <summary>FFmpeg diagnostics ({logs.length} recent lines)</summary>
            <pre>{logs.join('\n')}</pre>
          </details>
        ) : null}
      </div>
    </section>
  );
}

function generatedResultPreview(
  result: ExportResult,
  probe: MediaProbe | undefined
) {
  const element = document.createElement(
    result.mimeType.startsWith('video/') ? 'video' : 'audio'
  );
  return assessGeneratedResultPreview(
    {
      fileName: result.fileName,
      mimeType: result.mimeType,
      ...(probe ? { probe } : {}),
    },
    (mimeType) => element.canPlayType(mimeType)
  );
}

function isTerminalJob(job: MediaJob): boolean {
  return (
    job.status === 'completed' ||
    job.status === 'failed' ||
    job.status === 'cancelled'
  );
}

function formatJobTime(value: string): string {
  const date = new Date(value);
  return Number.isNaN(date.valueOf())
    ? value
    : date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}

function formatTagSummary(tags: Readonly<Record<string, string>>): string {
  const entries = Object.entries(tags);
  return entries.length === 0
    ? 'None reported'
    : entries.map(([key, value]) => `${key}=${value}`).join(' · ');
}

function enabledDispositions(
  dispositions: Readonly<Record<string, boolean>>
): string {
  const enabled = Object.entries(dispositions)
    .filter(([, value]) => value)
    .map(([key]) => key);
  return enabled.length > 0 ? enabled.join(', ') : 'none';
}
