import "server-only";

import { nanoid } from "nanoid";

import { extractFrame } from "@/lib/media-processing/frame-extraction";
import { scoreThumbnailCandidates } from "@/lib/thumbnail-scoring";
import { MediaProcessingError } from "@/lib/server/media-errors";
import type { ThumbnailCandidate, ThumbnailEnhancementOptions } from "@/types/media-processing";

interface GenerateThumbnailCandidatesOptions {
  inputPath: string;
  durationSeconds: number;
  candidateCount?: number;
  sampleCount?: number;
  seed?: number;
  enhancements?: ThumbnailEnhancementOptions;
}

interface GenerateThumbnailCandidatesResult {
  candidates: ThumbnailCandidate[];
  generatedPaths: string[];
  seed: number;
}

function createSeededRandom(seed: number) {
  let state = seed >>> 0;

  return () => {
    state += 0x6d2b79f5;
    let value = Math.imul(state ^ (state >>> 15), state | 1);
    value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
    return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
  };
}

function clampNumber(value: number, min: number, max: number) {
  return Math.min(max, Math.max(min, value));
}

function buildSampleTimestamps(
  durationSeconds: number,
  sampleCount: number,
  seed: number
) {
  const random = createSeededRandom(seed);
  const timestamps: number[] = [];
  const safeDuration = Math.max(durationSeconds, 0.5);
  const start = Math.min(0.35, safeDuration * 0.08);
  const end = Math.max(safeDuration - 0.35, 0.4);

  for (let index = 0; index < sampleCount; index += 1) {
    const ratio = (index + 1) / (sampleCount + 1);
    const base = start + (end - start) * ratio;
    const jitter = (random() - 0.5) * Math.max(safeDuration / sampleCount, 0.3) * 0.55;
    timestamps.push(clampNumber(base + jitter, 0.05, Math.max(safeDuration - 0.05, 0.05)));
  }

  return [...new Set(timestamps.map((value) => Number(value.toFixed(3))))].sort((a, b) => a - b);
}

function pickDistributedCandidates(
  timestamps: number[],
  limit: number,
  durationSeconds: number
) {
  const selected: number[] = [];
  const minGap = Math.max(durationSeconds / Math.max(limit * 2, 1), 0.9);

  for (const timestamp of timestamps) {
    const tooClose = selected.some((picked) => Math.abs(picked - timestamp) < minGap);
    if (tooClose) {
      continue;
    }

    selected.push(timestamp);
    if (selected.length >= limit) {
      break;
    }
  }

  if (selected.length < limit) {
    for (const timestamp of timestamps) {
      if (selected.includes(timestamp)) {
        continue;
      }

      selected.push(timestamp);
      if (selected.length >= limit) {
        break;
      }
    }
  }

  return selected.slice(0, limit);
}

function buildFilterChain(options: ThumbnailEnhancementOptions) {
  const filters: string[] = [];

  if (options.sharpen) {
    filters.push("unsharp=5:5:0.8:3:3:0.4");
  }

  if (options.boost) {
    filters.push("eq=contrast=1.08:brightness=0.03:saturation=1.06");
  }

  if (options.titleSafeOverlay) {
    filters.push("drawbox=x=iw*0.08:y=ih*0.08:w=iw*0.84:h=ih*0.84:color=white@0.5:t=2");
    filters.push("drawbox=x=iw*0.08:y=ih*0.08:w=iw*0.84:h=ih*0.22:color=yellow@0.48:t=2");
  }

  return filters;
}

export async function generateThumbnailCandidatesFoundation({
  inputPath,
  durationSeconds,
  candidateCount = 4,
  sampleCount = 18,
  seed = Date.now(),
  enhancements = {}
}: GenerateThumbnailCandidatesOptions): Promise<GenerateThumbnailCandidatesResult> {
  if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) {
    throw new MediaProcessingError({
      message: "Valid duration is required for thumbnail candidate generation.",
      code: "invalid_input",
      statusCode: 400
    });
  }

  const scoringSeed = Math.floor(seed);
  const extractionCount = Math.max(sampleCount, candidateCount * 3);
  const timestamps = buildSampleTimestamps(durationSeconds, extractionCount, scoringSeed);
  const selectedTimestamps = pickDistributedCandidates(timestamps, extractionCount, durationSeconds);
  const filters = buildFilterChain(enhancements);

  const extractedCandidates: Array<{
    id: string;
    timestampSeconds: number;
    outputPath: string;
  }> = [];

  const generatedPaths: string[] = [];
  for (let index = 0; index < selectedTimestamps.length; index += 1) {
    const timestamp = selectedTimestamps[index];
    const frameResult = await extractFrame({
      inputPath,
      timestampSeconds: timestamp,
      outputFormat: "jpg",
      filters
    });

    generatedPaths.push(frameResult.outputPath);
    extractedCandidates.push({
      id: `thumb-${nanoid(8)}`,
      timestampSeconds: timestamp,
      outputPath: frameResult.outputPath
    });
  }

  const { selected } = await scoreThumbnailCandidates(extractedCandidates, {
    candidateCount,
    duplicateHashDistance: 9
  });

  const finalCandidates: ThumbnailCandidate[] = selected.map((candidate) => ({
    id: candidate.id,
    timestampSeconds: candidate.timestampSeconds,
    outputPath: candidate.outputPath,
    score: candidate.score,
    rationale: candidate.rationale
  }));

  return {
    candidates: finalCandidates,
    generatedPaths,
    seed: scoringSeed
  };
}
