import type { AvProjectV1, LegacyAvProjectV0 } from '../project.types';

export interface ProjectMigrationWarning {
  code: 'renamed-field' | 'defaulted-field' | 'source-reattachment-required';
  path: string;
  message: string;
}

export interface V0MigrationResult {
  project: AvProjectV1;
  warnings: ProjectMigrationWarning[];
}

export function migrateV0ToV1(input: LegacyAvProjectV0): V0MigrationResult {
  const warnings: ProjectMigrationWarning[] = [];
  const rawAssets = Array.isArray(input.assets)
    ? input.assets
    : Array.isArray(input.media)
      ? input.media
      : [];
  const rawTimeline = Array.isArray(input.timeline)
    ? input.timeline
    : Array.isArray(input.clips)
      ? input.clips
      : [];
  if (input.title !== undefined && input.name === undefined) {
    warnings.push({
      code: 'renamed-field',
      path: 'title',
      message: 'Migrated the legacy "title" field to "name".',
    });
  }
  if (input.media !== undefined && input.assets === undefined) {
    warnings.push({
      code: 'renamed-field',
      path: 'media',
      message: 'Migrated the legacy "media" collection to "assets".',
    });
  }
  if (input.clips !== undefined && input.timeline === undefined) {
    warnings.push({
      code: 'renamed-field',
      path: 'clips',
      message: 'Migrated the legacy "clips" collection to "timeline".',
    });
  }

  const project: AvProjectV1 = {
    schemaVersion: 1,
    id: stringValue(input.id),
    name: stringValue(input.name ?? input.title ?? 'Untitled project'),
    createdAt: stringValue(input.createdAt),
    updatedAt: stringValue(input.updatedAt),
    assets: rawAssets.map((asset, index) =>
      migrateAsset(asset, index, warnings)
    ),
    timeline: rawTimeline.map((clip, index) => migrateClip(clip, index)),
    output: migrateOutput(input.output, warnings),
    metadata: migrateMetadata(input.metadata),
    chapters: (Array.isArray(input.chapters) ? input.chapters : []).map(
      migrateChapter
    ),
    subtitles: (Array.isArray(input.subtitles) ? input.subtitles : []).map(
      migrateSubtitle
    ),
    ...(isRecord(input.ui) ? { ui: input.ui as AvProjectV1['ui'] } : {}),
  };

  return { project, warnings };
}

function migrateAsset(
  value: unknown,
  index: number,
  warnings: ProjectMigrationWarning[]
): AvProjectV1['assets'][number] {
  const asset = isRecord(value) ? value : {};
  const originalName = stringValue(
    asset.originalName ?? asset.fileName ?? asset.name ?? `source-${index}`
  );
  if (asset.originalName === undefined) {
    warnings.push({
      code: 'renamed-field',
      path: `assets[${index}].originalName`,
      message: 'Migrated a legacy source filename field.',
    });
  }
  return {
    id: stringValue(asset.id ?? `asset-${index}`),
    originalName,
    size: numberValue(asset.size),
    lastModified: numberValue(asset.lastModified),
    mimeType: stringValue(asset.mimeType ?? asset.type ?? ''),
    sourceStatus: asset.sourceStatus === 'attached' ? 'attached' : 'missing',
    ...(isRecord(asset.probe)
      ? {
          probe:
            asset.probe as unknown as AvProjectV1['assets'][number]['probe'],
        }
      : {}),
    ...(isRecord(asset.hash)
      ? {
          hash: asset.hash as unknown as AvProjectV1['assets'][number]['hash'],
        }
      : {}),
  };
}

function migrateClip(
  value: unknown,
  index: number
): AvProjectV1['timeline'][number] {
  const clip = isRecord(value) ? value : {};
  return {
    id: stringValue(clip.id ?? `clip-${index}`),
    assetId: stringValue(clip.assetId),
    sourceInSeconds: numberValue(
      clip.sourceInSeconds ?? clip.inSeconds ?? clip.startSeconds
    ),
    sourceOutSeconds: numberValue(
      clip.sourceOutSeconds ?? clip.outSeconds ?? clip.endSeconds
    ),
    ...(isRecord(clip.crop)
      ? {
          crop: clip.crop as unknown as AvProjectV1['timeline'][number]['crop'],
        }
      : {}),
    ...(isRecord(clip.resize)
      ? {
          resize:
            clip.resize as unknown as AvProjectV1['timeline'][number]['resize'],
        }
      : {}),
    ...(clip.rotation !== undefined
      ? {
          rotation:
            clip.rotation as AvProjectV1['timeline'][number]['rotation'],
        }
      : {}),
    ...(clip.frameRate !== undefined
      ? { frameRate: numberValue(clip.frameRate) }
      : {}),
    ...(isRecord(clip.audio)
      ? {
          audio:
            clip.audio as unknown as AvProjectV1['timeline'][number]['audio'],
        }
      : {}),
    ...(isRecord(clip.video)
      ? {
          video:
            clip.video as unknown as AvProjectV1['timeline'][number]['video'],
        }
      : {}),
  };
}

function migrateOutput(
  value: unknown,
  warnings: ProjectMigrationWarning[]
): AvProjectV1['output'] {
  const output = isRecord(value) ? value : {};
  const defaultsUsed = [
    'presetId',
    'fileName',
    'container',
    'videoEnabled',
    'audioEnabled',
  ].some((field) => output[field] === undefined);
  if (defaultsUsed) {
    warnings.push({
      code: 'defaulted-field',
      path: 'output',
      message: 'Filled missing legacy output settings with safe defaults.',
    });
  }
  return {
    presetId: stringValue(output.presetId ?? 'mp4-h264-balanced'),
    fileName: stringValue(output.fileName ?? 'output.mp4'),
    container: stringValue(output.container ?? 'mp4'),
    videoEnabled:
      typeof output.videoEnabled === 'boolean' ? output.videoEnabled : true,
    audioEnabled:
      typeof output.audioEnabled === 'boolean' ? output.audioEnabled : true,
    ...(finiteNumber(output.width) ? { width: Number(output.width) } : {}),
    ...(finiteNumber(output.height) ? { height: Number(output.height) } : {}),
    ...(finiteNumber(output.frameRate)
      ? { frameRate: Number(output.frameRate) }
      : {}),
    sampleRate: finiteNumber(output.sampleRate)
      ? Number(output.sampleRate)
      : 48_000,
    channelLayout:
      output.channelLayout === 'mono' || output.channelLayout === '5.1'
        ? output.channelLayout
        : 'stereo',
    missingAudioPolicy:
      output.missingAudioPolicy === 'drop-all' ||
      output.missingAudioPolicy === 'reject'
        ? output.missingAudioPolicy
        : 'insert-silence',
  };
}

function migrateMetadata(value: unknown): AvProjectV1['metadata'] {
  const metadata = isRecord(value) ? value : {};
  return {
    ...(typeof metadata.title === 'string' ? { title: metadata.title } : {}),
    ...(typeof metadata.artist === 'string' ? { artist: metadata.artist } : {}),
    ...(typeof metadata.album === 'string' ? { album: metadata.album } : {}),
    ...(typeof metadata.comment === 'string'
      ? { comment: metadata.comment }
      : {}),
    custom: isStringRecord(metadata.custom) ? metadata.custom : {},
  };
}

function migrateChapter(
  value: unknown,
  index: number
): AvProjectV1['chapters'][number] {
  const chapter = isRecord(value) ? value : {};
  return {
    id: stringValue(chapter.id ?? `chapter-${index}`),
    startSeconds: numberValue(chapter.startSeconds ?? chapter.start),
    endSeconds: numberValue(chapter.endSeconds ?? chapter.end),
    title: stringValue(chapter.title ?? `Chapter ${index + 1}`),
    timeBase:
      typeof chapter.timeBase === 'string' ? chapter.timeBase : '1/1000',
    ...(isStringRecord(chapter.metadata) ? { metadata: chapter.metadata } : {}),
  };
}

function migrateSubtitle(
  value: unknown,
  index: number
): AvProjectV1['subtitles'][number] {
  const subtitle = isRecord(value) ? value : {};
  return {
    id: stringValue(subtitle.id ?? `subtitle-${index}`),
    ...(typeof subtitle.assetId === 'string'
      ? { assetId: subtitle.assetId }
      : {}),
    ...(typeof subtitle.sourceName === 'string'
      ? { sourceName: subtitle.sourceName }
      : {}),
    ...(subtitle.sourceFormat === 'srt' ||
    subtitle.sourceFormat === 'vtt' ||
    subtitle.sourceFormat === 'ass'
      ? { sourceFormat: subtitle.sourceFormat }
      : {}),
    ...(typeof subtitle.language === 'string'
      ? { language: subtitle.language }
      : {}),
    ...(typeof subtitle.title === 'string' ? { title: subtitle.title } : {}),
    ...(typeof subtitle.offsetSeconds === 'number' &&
    Number.isFinite(subtitle.offsetSeconds)
      ? { offsetSeconds: subtitle.offsetSeconds }
      : {}),
    ...(typeof subtitle.default === 'boolean'
      ? { default: subtitle.default }
      : {}),
    ...(typeof subtitle.forced === 'boolean'
      ? { forced: subtitle.forced }
      : {}),
    mode:
      subtitle.mode === 'mux' || subtitle.mode === 'burn-in'
        ? subtitle.mode
        : 'exclude',
    ...(subtitle.sourceStatus === 'attached' ||
    subtitle.sourceStatus === 'missing'
      ? { sourceStatus: subtitle.sourceStatus }
      : {}),
  };
}

function numberValue(value: unknown): number {
  return typeof value === 'number' ? value : Number(value ?? 0);
}

function finiteNumber(value: unknown): boolean {
  return typeof value === 'number' && Number.isFinite(value);
}

function stringValue(value: unknown): string {
  return typeof value === 'string' ? value : String(value ?? '');
}

function isStringRecord(value: unknown): value is Record<string, string> {
  return (
    isRecord(value) &&
    Object.values(value).every((entry) => typeof entry === 'string')
  );
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}
