import { FFMPEG_CORE_VERSION, FFMPEG_WRAPPER_VERSION } from '../version';
import type { EnginePreference, EngineState } from '../ffmpeg/ffmpeg.types';
import {
  describeEngineMode,
  detectEngineEnvironment,
} from '../ffmpeg/engine-mode';
import type { ResourcePolicy } from '../storage';
import { Icon } from './Icon';

export interface CapabilityPanelProps {
  state: EngineState;
  preference: EnginePreference;
  resourcePolicy: ResourcePolicy;
  deviceMemoryGiB?: number;
  onPreferenceChange: (preference: EnginePreference) => void;
  onInitialize: () => void;
}

export function CapabilityPanel({
  state,
  preference,
  resourcePolicy,
  deviceMemoryGiB,
  onPreferenceChange,
  onInitialize,
}: CapabilityPanelProps) {
  const environment = detectEngineEnvironment();
  const mode =
    state.status === 'ready' ||
    state.status === 'loading' ||
    state.status === 'running'
      ? state.mode
      : undefined;

  return (
    <details className="capability-panel">
      <summary>
        <span>
          <Icon name="settings" />
          Engine & browser
        </span>
        <span className={`engine-pill engine-pill--${state.status}`}>
          {state.status === 'idle'
            ? 'Not loaded'
            : state.status === 'loading'
              ? 'Loading'
              : state.status === 'ready'
                ? describeEngineMode(state.mode)
                : state.status === 'running'
                  ? 'Processing'
                  : state.status === 'recovering'
                    ? 'Recovering'
                    : 'Unavailable'}
        </span>
      </summary>
      <div className="capability-panel__body">
        <label>
          Engine mode
          <select
            value={preference}
            disabled={state.status === 'running'}
            onChange={(event) =>
              onPreferenceChange(event.currentTarget.value as EnginePreference)
            }
          >
            <option value="automatic">Automatic</option>
            <option value="prefer-multithread">Prefer multithread</option>
            <option value="force-single-thread">Force single-thread</option>
          </select>
        </label>
        <dl className="capability-grid">
          <div>
            <dt>Selected</dt>
            <dd>{mode ? describeEngineMode(mode) : 'After first import'}</dd>
          </div>
          <div>
            <dt>Isolation</dt>
            <dd>
              {environment.crossOriginIsolated ? 'Enabled' : 'Unavailable'}
            </dd>
          </div>
          <div>
            <dt>Processors</dt>
            <dd>{environment.hardwareConcurrency ?? 'Not exposed'}</dd>
          </div>
          <div>
            <dt>Wrapper / core</dt>
            <dd>
              {FFMPEG_WRAPPER_VERSION} / {FFMPEG_CORE_VERSION}
            </dd>
          </div>
          {state.status === 'ready' ? (
            <>
              <div>
                <dt>FFmpeg</dt>
                <dd title={state.capabilities.versionText}>
                  {versionLine(state.capabilities.versionText)}
                </dd>
              </div>
              <div>
                <dt>Demuxers</dt>
                <dd>{state.capabilities.demuxers.size}</dd>
              </div>
              <div>
                <dt>Muxers</dt>
                <dd>{state.capabilities.muxers.size}</dd>
              </div>
              <div>
                <dt>Decoders</dt>
                <dd>{state.capabilities.decoders.size}</dd>
              </div>
              <div>
                <dt>Encoders</dt>
                <dd>{state.capabilities.encoders.size}</dd>
              </div>
              <div>
                <dt>Filters</dt>
                <dd>{state.capabilities.filters.size}</dd>
              </div>
            </>
          ) : null}
        </dl>
        <section
          className="capability-limits"
          aria-labelledby="active-resource-limits"
        >
          <div className="capability-limits__heading">
            <h3 id="active-resource-limits">Active resource limits</h3>
            <span>
              {deviceMemoryGiB === undefined
                ? 'Conservative defaults'
                : `Adjusted for ${deviceMemoryGiB} GiB device memory`}
            </span>
          </div>
          <dl className="capability-grid capability-grid--limits">
            <div>
              <dt>Input warning / file limit</dt>
              <dd>
                {formatBytes(resourcePolicy.softInputBytes)} /{' '}
                {formatBytes(resourcePolicy.hardSingleInputBytes)}
              </dd>
            </div>
            <div>
              <dt>Total input limit</dt>
              <dd>{formatBytes(resourcePolicy.hardTotalInputBytes)}</dd>
            </div>
            <div>
              <dt>Output warning / limit</dt>
              <dd>
                {formatBytes(resourcePolicy.softOutputEstimateBytes)} /{' '}
                {formatBytes(resourcePolicy.hardOutputEstimateBytes)}
              </dd>
            </div>
            <div>
              <dt>Image limit</dt>
              <dd>
                {resourcePolicy.maxImageDimension.toLocaleString()} px / side
              </dd>
            </div>
            <div>
              <dt>Timeline / streams</dt>
              <dd>
                {resourcePolicy.maxTimelineClips} clips /{' '}
                {resourcePolicy.maxStreamsPerInput} streams
              </dd>
            </div>
            <div>
              <dt>Generated files / images</dt>
              <dd>
                {resourcePolicy.maxGeneratedOutputs} /{' '}
                {resourcePolicy.maxThumbnailCount}
              </dd>
            </div>
            <div>
              <dt>FFmpeg concurrency</dt>
              <dd>{resourcePolicy.maxConcurrentFFmpegJobs}</dd>
            </div>
            <div>
              <dt>Runtime limit</dt>
              <dd>{formatDuration(resourcePolicy.maxJobRuntimeMs)}</dd>
            </div>
          </dl>
          <p>
            These product safety limits are conservative estimates, not media
            format limits or a guarantee that a browser job will complete. The
            generated-file limit reserves one ZIP entry for the optional export
            report.
          </p>
        </section>
        {state.status === 'idle' ? (
          <button className="button button--secondary" onClick={onInitialize}>
            Initialize engine
          </button>
        ) : null}
        {state.status === 'loading' ? (
          <div className="load-stage" role="status">
            <progress value={state.progress} max="1" />
            <span>{state.stage ?? 'Loading local engine…'}</span>
          </div>
        ) : null}
        {state.status === 'ready' && state.warning ? (
          <p className="notice notice--warning">
            <Icon name="warning" />
            {state.warning.message}
          </p>
        ) : null}
        {state.status === 'ready' ? (
          <>
            <details className="capability-build">
              <summary>
                Detected capability inventory (
                {capabilityCount(state.capabilities)} entries)
              </summary>
              <pre>{capabilityInventory(state.capabilities)}</pre>
            </details>
            <details className="capability-build">
              <summary>
                FFmpeg build configuration (
                {state.capabilities.buildConfiguration.length} flags)
              </summary>
              <pre>
                {state.capabilities.buildConfiguration.length > 0
                  ? state.capabilities.buildConfiguration.join('\n')
                  : 'No build flags were reported.'}
              </pre>
            </details>
          </>
        ) : null}
        {state.status === 'error' ? (
          <div className="capability-error" role="alert">
            <p className="notice notice--error">
              <Icon name="warning" />
              <span>
                <strong>Engine initialization failed</strong>
                {state.error.message}
              </span>
            </p>
            <dl>
              <div>
                <dt>Error code</dt>
                <dd>{state.error.code}</dd>
              </div>
              {state.error.exitCode === undefined ? null : (
                <div>
                  <dt>FFmpeg exit code</dt>
                  <dd>{state.error.exitCode}</dd>
                </div>
              )}
            </dl>
            {state.error.details ? (
              <details>
                <summary>Technical details</summary>
                <pre>{state.error.details}</pre>
              </details>
            ) : null}
            <button className="button button--secondary" onClick={onInitialize}>
              Retry initialization
            </button>
          </div>
        ) : null}
      </div>
    </details>
  );
}

function capabilityCount(
  capabilities: Extract<EngineState, { status: 'ready' }>['capabilities']
): number {
  return (
    capabilities.demuxers.size +
    capabilities.muxers.size +
    capabilities.decoders.size +
    capabilities.encoders.size +
    capabilities.filters.size
  );
}

function capabilityInventory(
  capabilities: Extract<EngineState, { status: 'ready' }>['capabilities']
): string {
  return (
    [
      ['Demuxers', capabilities.demuxers],
      ['Muxers', capabilities.muxers],
      ['Decoders', capabilities.decoders],
      ['Encoders', capabilities.encoders],
      ['Filters', capabilities.filters],
    ] as const
  )
    .map(
      ([label, values]) =>
        `${label} (${values.size})\n${[...values].sort().join(', ')}`
    )
    .join('\n\n');
}

function versionLine(versionText: string): string {
  return (
    versionText
      .split(/\r?\n/u)
      .map((line) => line.trim())
      .find((line) => /^ffmpeg version\b/iu.test(line)) ??
    versionText.trim().split(/\r?\n/u)[0] ??
    'Unknown'
  );
}

function formatBytes(bytes: number): string {
  const mebibytes = bytes / (1024 * 1024);
  if (mebibytes >= 1024) {
    return `${new Intl.NumberFormat(undefined, {
      maximumFractionDigits: 1,
    }).format(mebibytes / 1024)} GiB`;
  }
  return `${new Intl.NumberFormat(undefined, {
    maximumFractionDigits: 0,
  }).format(mebibytes)} MiB`;
}

function formatDuration(milliseconds: number): string {
  const hours = milliseconds / (60 * 60 * 1000);
  return Number.isInteger(hours)
    ? `${hours} h`
    : `${new Intl.NumberFormat(undefined, {
        maximumFractionDigits: 1,
      }).format(hours)} h`;
}
