import { useMemo, useState } from 'react';
import type { ImportedMediaAsset } from '../app/application-state';
import type { EngineState } from '../ffmpeg/ffmpeg.types';
import type { MediaStreamProbe } from '../media';
import type { StreamSelection } from '../commands/command-utils';
import {
  validateRemuxCompatibility,
  type RemuxStream,
} from '../commands/remux';
import {
  requirementsForUserPreset,
  type ExportPreset,
} from '../commands/convert';
import {
  BUILT_IN_PRESETS,
  presetAvailability,
} from '../presets/preset-registry';
import { formatBytes, formatDuration } from '../app/application-state';
import { FFMPEG_OPUS_REGRESSION_VERIFIED } from '../version';
import { Icon } from './Icon';

export type QuickOperation = 'convert' | 'remux';

export interface QuickExportConfiguration {
  operation: QuickOperation;
  preset?: ExportPreset;
  remuxContainer?: 'mp4' | 'webm' | 'matroska' | 'ogg';
  streamSelection: StreamSelection;
  removeMetadata: boolean;
  removeChapters: boolean;
}

export interface QuickConvertProps {
  asset?: ImportedMediaAsset;
  engineState: EngineState;
  busy: boolean;
  queueing?: boolean;
  presets?: readonly ExportPreset[];
  onExport: (configuration: QuickExportConfiguration) => void;
}

export function QuickConvert({
  asset,
  engineState,
  busy,
  queueing = false,
  presets = BUILT_IN_PRESETS,
  onExport,
}: QuickConvertProps) {
  const [operation, setOperation] = useState<QuickOperation>('convert');
  const [presetId, setPresetId] = useState('mp4-h264-balanced');
  const [remuxContainer, setRemuxContainer] =
    useState<QuickExportConfiguration['remuxContainer']>('matroska');
  const [removeMetadata, setRemoveMetadata] = useState(false);
  const [removeChapters, setRemoveChapters] = useState(false);
  const [streamOverride, setStreamOverride] = useState<{
    readonly assetId: string;
    readonly indices: ReadonlySet<number>;
  }>();

  const preset = presets.find((entry) => entry.id === presetId);
  const streams = useMemo(() => asset?.probe?.streams ?? [], [asset?.probe]);
  const requestedStreams = useMemo(
    () =>
      streamOverride !== undefined &&
      asset !== undefined &&
      streamOverride.assetId === asset.id
        ? streamOverride.indices
        : new Set(streams.map((stream) => stream.index)),
    [asset, streamOverride, streams]
  );
  const streamUnavailableReasons = useMemo(
    () =>
      new Map(
        streams.flatMap((stream) => {
          const reason = quickStreamUnavailableReason(
            stream,
            operation,
            preset
          );
          return reason ? ([[stream.index, reason] as const] as const) : [];
        })
      ),
    [operation, preset, streams]
  );
  const selectedStreams = useMemo(
    () =>
      new Set(
        [...requestedStreams].filter(
          (index) => !streamUnavailableReasons.has(index)
        )
      ),
    [requestedStreams, streamUnavailableReasons]
  );
  const effectiveSelection = useMemo(() => {
    return {
      video: streams
        .filter(
          (stream) =>
            stream.type === 'video' && selectedStreams.has(stream.index)
        )
        .map((stream) => stream.index),
      audio: streams
        .filter(
          (stream) =>
            stream.type === 'audio' && selectedStreams.has(stream.index)
        )
        .map((stream) => stream.index),
      subtitles: streams
        .filter(
          (stream) =>
            stream.type === 'subtitle' && selectedStreams.has(stream.index)
        )
        .map((stream) => stream.index),
      attachments: streams
        .filter(
          (stream) =>
            stream.type === 'attachment' && selectedStreams.has(stream.index)
        )
        .map((stream) => stream.index),
      data: streams
        .filter(
          (stream) =>
            stream.type === 'data' && selectedStreams.has(stream.index)
        )
        .map((stream) => stream.index),
    };
  }, [selectedStreams, streams]);

  const presetRequirements =
    preset && 'builtIn' in preset
      ? preset.requirements
      : preset
        ? requirementsForUserPreset(preset)
        : undefined;
  const availability =
    presetRequirements && engineState.status === 'ready'
      ? presetAvailability(
          { requirements: presetRequirements },
          engineState.capabilities
        )
      : undefined;
  const opusBlockedByPinnedCore =
    preset?.audio?.codec === 'libopus' && !FFMPEG_OPUS_REGRESSION_VERIFIED;
  const presetNeedsMissingAudio =
    preset?.kind === 'audio' && effectiveSelection.audio.length === 0;
  const remuxStreams: readonly RemuxStream[] = streams.flatMap((stream) =>
    stream.type === 'video' ||
    stream.type === 'audio' ||
    stream.type === 'subtitle' ||
    stream.type === 'attachment' ||
    stream.type === 'data'
      ? [
          {
            index: stream.index,
            kind: stream.type,
            codec: stream.codecName ?? 'unknown',
          },
        ]
      : []
  );
  const remuxDiagnostics = remuxContainer
    ? validateRemuxCompatibility(
        {
          streams: remuxStreams,
          hasChapters: Boolean(asset?.probe?.chapters.length),
        },
        remuxContainer,
        effectiveSelection
      )
    : [];
  const remuxErrors = remuxDiagnostics.filter(
    (diagnostic) => diagnostic.severity === 'error'
  );
  const remuxMuxerMissing =
    remuxContainer !== undefined &&
    engineState.status === 'ready' &&
    !engineState.capabilities.muxers.has(remuxContainer);
  const remuxUnavailableReason = remuxMuxerMissing
    ? `The loaded core is missing muxer ${remuxContainer}.`
    : remuxErrors.map((diagnostic) => diagnostic.message).join(' ');
  const hasSelectedStream = selectedStreams.size > 0;
  const canExport =
    asset?.phase === 'ready' &&
    !busy &&
    hasSelectedStream &&
    (operation === 'remux'
      ? !remuxMuxerMissing && remuxErrors.length === 0
      : preset !== undefined &&
        availability?.status !== 'unavailable' &&
        !opusBlockedByPinnedCore &&
        !presetNeedsMissingAudio);

  const presetOption = (entry: ExportPreset) => {
    const requirements =
      'builtIn' in entry
        ? entry.requirements
        : requirementsForUserPreset(entry);
    const entryAvailability =
      engineState.status === 'ready'
        ? presetAvailability({ requirements }, engineState.capabilities)
        : undefined;
    const reason =
      entry.audio?.codec === 'libopus' && !FFMPEG_OPUS_REGRESSION_VERIFIED
        ? 'the reviewed core has not passed the ST/MT Opus regression encode'
        : entryAvailability?.status === 'unavailable'
          ? entryAvailability.reasons.join(', ')
          : undefined;
    return {
      disabled: reason !== undefined,
      label: reason ? `${entry.name} — unavailable: ${reason}` : entry.name,
    };
  };

  return (
    <section className="panel quick-panel" aria-labelledby="quick-title">
      <header className="panel__header">
        <div>
          <p className="eyebrow">One source · one output</p>
          <h2 id="quick-title">Quick Convert</h2>
        </div>
        <span className="privacy-badge">
          <span />
          Local processing
        </span>
      </header>

      {asset ? (
        <div className="source-summary">
          <span className="source-summary__icon">
            <Icon
              name={
                asset.probe?.streams.some((stream) => stream.type === 'video')
                  ? 'video'
                  : 'audio'
              }
            />
          </span>
          <div>
            <strong>{asset.file.name}</strong>
            <span>
              {formatBytes(asset.file.size)} ·{' '}
              {formatDuration(asset.probe?.durationSeconds)}
            </span>
          </div>
          <span className={`phase phase--${asset.phase}`}>
            {asset.phase === 'ready'
              ? 'Ready'
              : asset.phase === 'error'
                ? 'Could not inspect'
                : 'Preparing'}
          </span>
        </div>
      ) : (
        <p className="panel-empty">
          Add and select a media source to continue.
        </p>
      )}

      <div className="segmented segmented--compact" aria-label="Output method">
        <button
          type="button"
          className={operation === 'convert' ? 'is-active' : ''}
          onClick={() => setOperation('convert')}
        >
          Re-encode
        </button>
        <button
          type="button"
          className={operation === 'remux' ? 'is-active' : ''}
          onClick={() => setOperation('remux')}
        >
          Fast remux
        </button>
      </div>

      <div className="form-grid">
        {operation === 'convert' ? (
          <label className="field field--wide">
            <span>Export preset</span>
            <select
              value={presetId}
              onChange={(event) => setPresetId(event.currentTarget.value)}
            >
              <optgroup label="Video">
                {presets
                  .filter((entry) => entry.kind !== 'audio')
                  .map((entry) => {
                    const option = presetOption(entry);
                    return (
                      <option
                        key={entry.id}
                        value={entry.id}
                        disabled={option.disabled}
                      >
                        {option.label}
                      </option>
                    );
                  })}
              </optgroup>
              <optgroup label="Audio">
                {presets
                  .filter((entry) => entry.kind === 'audio')
                  .map((entry) => {
                    const option = presetOption(entry);
                    return (
                      <option
                        key={entry.id}
                        value={entry.id}
                        disabled={option.disabled}
                      >
                        {option.label}
                      </option>
                    );
                  })}
              </optgroup>
            </select>
            <small>
              {preset?.description ??
                'A validated user-created local export preset.'}
            </small>
          </label>
        ) : (
          <label className="field field--wide">
            <span>Target container</span>
            <select
              value={remuxContainer}
              onChange={(event) =>
                setRemuxContainer(
                  event.currentTarget
                    .value as QuickExportConfiguration['remuxContainer']
                )
              }
            >
              {(
                [
                  ['matroska', 'Matroska (.mkv)'],
                  ['mp4', 'MP4 (.mp4)'],
                  ['webm', 'WebM (.webm)'],
                  ['ogg', 'Ogg (.ogg)'],
                ] as const
              ).map(([container, label]) => {
                const diagnostics = validateRemuxCompatibility(
                  {
                    streams: remuxStreams,
                    hasChapters: Boolean(asset?.probe?.chapters.length),
                  },
                  container,
                  effectiveSelection
                );
                const missingMuxer =
                  engineState.status === 'ready' &&
                  !engineState.capabilities.muxers.has(container);
                const reason = [
                  ...(missingMuxer ? [`missing muxer ${container}.`] : []),
                  ...diagnostics
                    .filter((entry) => entry.severity === 'error')
                    .map((entry) => entry.message),
                ].join(' ');
                return (
                  <option
                    key={container}
                    value={container}
                    disabled={Boolean(reason)}
                  >
                    {reason ? `${label} — unavailable: ${reason}` : label}
                  </option>
                );
              })}
            </select>
            <small>Copies compatible streams without quality loss.</small>
          </label>
        )}

        <fieldset className="stream-picker field--wide">
          <legend>Streams</legend>
          {streams.length > 0 ? (
            streams.map((stream) => {
              const checked = selectedStreams.has(stream.index);
              const unavailableReason = streamUnavailableReasons.get(
                stream.index
              );
              return (
                <label key={stream.index}>
                  <input
                    type="checkbox"
                    checked={checked}
                    disabled={unavailableReason !== undefined}
                    onChange={() => {
                      const next = new Set(requestedStreams);
                      if (next.has(stream.index)) next.delete(stream.index);
                      else next.add(stream.index);
                      if (asset) {
                        setStreamOverride({
                          assetId: asset.id,
                          indices: next,
                        });
                      }
                    }}
                  />
                  <span className="stream-kind">{stream.type}</span>
                  <span>
                    #{stream.index} · {stream.codecName ?? 'unknown codec'}
                    {stream.language ? ` · ${stream.language}` : ''}
                    {unavailableReason ? (
                      <small>Unavailable: {unavailableReason}</small>
                    ) : null}
                  </span>
                </label>
              );
            })
          ) : (
            <p>Streams appear after local inspection.</p>
          )}
        </fieldset>

        <label className="check-field">
          <input
            type="checkbox"
            checked={removeMetadata}
            onChange={(event) => setRemoveMetadata(event.currentTarget.checked)}
          />
          Remove metadata
        </label>
        <label className="check-field">
          <input
            type="checkbox"
            checked={removeChapters}
            onChange={(event) => setRemoveChapters(event.currentTarget.checked)}
          />
          Remove chapters
        </label>
      </div>

      {availability?.status === 'unavailable' ? (
        <p className="notice notice--error">
          <Icon name="warning" />
          This core cannot run the selected preset:{' '}
          {availability.reasons.join(', ')}
        </p>
      ) : null}
      {operation === 'remux' && remuxUnavailableReason ? (
        <p className="notice notice--error">
          <Icon name="warning" />
          {remuxUnavailableReason}
        </p>
      ) : null}
      {operation === 'convert' && presetNeedsMissingAudio ? (
        <p className="notice notice--error">
          <Icon name="warning" />
          Select at least one audio stream for an audio-only preset.
        </p>
      ) : null}
      {!hasSelectedStream && streams.length > 0 ? (
        <p className="notice notice--error">
          <Icon name="warning" />
          Select at least one output stream.
        </p>
      ) : null}
      {opusBlockedByPinnedCore ? (
        <p className="notice notice--warning">
          <Icon name="warning" />
          Opus export is disabled until the reviewed core passes its non-empty
          encode and re-probe gate in both engine modes.
        </p>
      ) : null}

      <footer className="quick-panel__footer">
        <div>
          <strong>
            {preset && 'settingsSummary' in preset
              ? preset.settingsSummary[0]
              : preset
                ? 'Validated user preset'
                : 'Stream copy'}
          </strong>
          <span>Resource estimates are checked before allocation.</span>
        </div>
        <button
          className="button button--primary button--large"
          type="button"
          disabled={!canExport}
          onClick={() =>
            onExport({
              operation,
              preset,
              remuxContainer,
              streamSelection: effectiveSelection,
              removeMetadata,
              removeChapters,
            })
          }
        >
          <Icon name={busy || queueing ? 'queue' : 'sparkles'} />
          {busy
            ? 'Preparing engine…'
            : queueing
              ? 'Add to queue'
              : operation === 'remux'
                ? 'Remux'
                : 'Convert'}
        </button>
      </footer>
    </section>
  );
}

function quickStreamUnavailableReason(
  stream: MediaStreamProbe,
  operation: QuickOperation,
  preset: ExportPreset | undefined
): string | undefined {
  if (stream.type === 'unknown') {
    return 'unknown stream kinds cannot be mapped safely.';
  }
  if (operation === 'remux') {
    return undefined;
  }
  if (!preset) {
    return 'choose an export preset first.';
  }
  if (stream.type === 'video' && !preset.video) {
    return 'the selected preset creates audio only.';
  }
  if (stream.type === 'audio' && !preset.audio) {
    return 'the selected preset creates video or images without audio.';
  }
  if (stream.type === 'subtitle') {
    if (preset.subtitlePolicy === 'none') {
      return 'the selected preset explicitly omits subtitles.';
    }
    if (preset.subtitlePolicy === 'burn-in') {
      return 'burn-in requires an explicitly attached subtitle source.';
    }
  }
  if (
    (stream.type === 'attachment' || stream.type === 'data') &&
    preset.container !== 'matroska'
  ) {
    return `${stream.type} streams are only preserved by the reviewed Matroska conversion path.`;
  }
  return undefined;
}
