import { useEffect, useRef, useState } from "react";

type ProgressStatus =
  | "queued"
  | "running"
  | "submitting"
  | "completed"
  | "failed"
  | "success"
  | "error";
export type ProgressSource = "real" | "estimated";

interface SmoothedProgressOptions {
  creepSpeed?: number;
  chaseRate?: number;
  creepCeiling?: number;
  creepAhead?: number;
  completionDuration?: number;
  progressSource?: ProgressSource;
  startedAt?: number | string | Date | null;
  expectedDurationMs?: number | null;
  estimatedFloor?: number;
  estimatedCeiling?: number;
}

const DEFAULT_CREEP_SPEED = 0.5;
const DEFAULT_CHASE_RATE = 0.09;
const DEFAULT_CREEP_CEILING = 97;
const DEFAULT_CREEP_AHEAD = 25;
const DEFAULT_COMPLETION_DURATION = 450;
export const DEFAULT_IMAGE_GENERATION_EXPECTED_DURATION_MS = 120_000;
export const DEFAULT_VIDEO_GENERATION_EXPECTED_DURATION_MS = 240_000;
export const DEFAULT_SUPER_RESOLUTION_EXPECTED_DURATION_MS = 180_000;
export const DEFAULT_GENERATION_EXPECTED_DURATION_MS =
  DEFAULT_IMAGE_GENERATION_EXPECTED_DURATION_MS;
const DEFAULT_ESTIMATED_FLOOR = 12;
const DEFAULT_ESTIMATED_CEILING = 92;
const ESTIMATED_PROGRESS_CURVE = 2.4;
const MIN_EXPECTED_DURATION_MS = 1_000;

function isTerminal(status: ProgressStatus): boolean {
  return (
    status === "completed" ||
    status === "success" ||
    status === "failed" ||
    status === "error"
  );
}

function isSuccess(status: ProgressStatus): boolean {
  return status === "completed" || status === "success";
}

export function resolveProgressStartedAt(
  value: number | string | Date | null | undefined,
): number | null {
  if (value instanceof Date) {
    const time = value.getTime();
    return Number.isFinite(time) ? time : null;
  }

  if (typeof value === "number") {
    return Number.isFinite(value) && value > 0 ? value : null;
  }

  if (typeof value !== "string" || !value.trim()) return null;

  const normalized = value.trim();
  const localMatch = normalized.match(
    /^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})(?::(\d{2}))?/,
  );
  if (localMatch) {
    const [, year, month, day, hours, minutes, seconds] = localMatch;
    const time = new Date(
      Number(year),
      Number(month) - 1,
      Number(day),
      Number(hours),
      Number(minutes),
      Number(seconds || 0),
    ).getTime();
    return Number.isFinite(time) ? time : null;
  }

  const parsed = Date.parse(normalized);
  return Number.isFinite(parsed) ? parsed : null;
}

export function calculateEstimatedProgress(input: {
  nowMs: number;
  startedAtMs: number;
  expectedDurationMs?: number | null;
  floor?: number;
  ceiling?: number;
}): number {
  const floor = Math.max(
    0,
    Math.min(99, input.floor ?? DEFAULT_ESTIMATED_FLOOR),
  );
  const ceiling = Math.max(
    floor,
    Math.min(99, input.ceiling ?? DEFAULT_ESTIMATED_CEILING),
  );
  const duration = Math.max(
    MIN_EXPECTED_DURATION_MS,
    Number(input.expectedDurationMs) || DEFAULT_GENERATION_EXPECTED_DURATION_MS,
  );
  const elapsed = Math.max(0, input.nowMs - input.startedAtMs);
  const ratio = elapsed / duration;
  const eased = 1 - Math.exp(-ratio * ESTIMATED_PROGRESS_CURVE);
  return Math.min(ceiling, floor + eased * (ceiling - floor));
}

export function formatEstimatedRemainingLabel(input: {
  nowMs: number;
  startedAt: number | string | Date | null | undefined;
  expectedDurationMs?: number | null;
}): string | null {
  const startedAtMs = resolveProgressStartedAt(input.startedAt);
  if (!startedAtMs) return null;
  const duration = Math.max(
    MIN_EXPECTED_DURATION_MS,
    Number(input.expectedDurationMs) || DEFAULT_GENERATION_EXPECTED_DURATION_MS,
  );
  const remainingSeconds = Math.max(
    0,
    Math.ceil((startedAtMs + duration - input.nowMs) / 1000),
  );
  if (remainingSeconds <= 0) return "即将完成";
  if (remainingSeconds < 60) return `预计还需 ${remainingSeconds} 秒`;
  return `预计还需 ${Math.ceil(remainingSeconds / 60)} 分钟`;
}

/**
 * Smoothly interpolates between coarse server-reported progress values.
 * On completion, animates quickly to 100% instead of jumping.
 */
export function useSmoothedProgress(
  realProgress: number,
  status: ProgressStatus,
  options?: SmoothedProgressOptions,
): number {
  const creepSpeed = options?.creepSpeed ?? DEFAULT_CREEP_SPEED;
  const chaseRate = options?.chaseRate ?? DEFAULT_CHASE_RATE;
  const creepCeiling = options?.creepCeiling ?? DEFAULT_CREEP_CEILING;
  const creepAhead = options?.creepAhead ?? DEFAULT_CREEP_AHEAD;
  const completionDuration =
    options?.completionDuration ?? DEFAULT_COMPLETION_DURATION;
  const progressSource = options?.progressSource ?? "real";
  const startedAt = options?.startedAt;
  const expectedDurationMs =
    options?.expectedDurationMs ?? DEFAULT_GENERATION_EXPECTED_DURATION_MS;
  const estimatedFloor = options?.estimatedFloor ?? DEFAULT_ESTIMATED_FLOOR;
  const estimatedCeiling =
    options?.estimatedCeiling ?? DEFAULT_ESTIMATED_CEILING;

  const [displayed, setDisplayed] = useState(0);
  const rafRef = useRef(0);
  const targetRef = useRef(realProgress);
  const statusRef = useRef(status);
  const progressSourceRef = useRef(progressSource);
  const expectedDurationMsRef = useRef(expectedDurationMs);
  const estimatedFloorRef = useRef(estimatedFloor);
  const estimatedCeilingRef = useRef(estimatedCeiling);
  const estimatedStartedAtRef = useRef<number | null>(null);
  const completionStartRef = useRef<number | null>(null);
  const completionBaseRef = useRef(0);

  useEffect(() => {
    targetRef.current = realProgress;
    statusRef.current = status;
    progressSourceRef.current = progressSource;
    expectedDurationMsRef.current = expectedDurationMs;
    estimatedFloorRef.current = estimatedFloor;
    estimatedCeilingRef.current = estimatedCeiling;

    if (progressSource === "estimated" && !isTerminal(status)) {
      estimatedStartedAtRef.current =
        resolveProgressStartedAt(startedAt) ??
        estimatedStartedAtRef.current ??
        Date.now();
    }

    if (isSuccess(status) && completionStartRef.current === null) {
      completionStartRef.current = performance.now();
      completionBaseRef.current = displayed;
    } else if (!isSuccess(status)) {
      completionStartRef.current = null;
    }
  }, [
    displayed,
    estimatedCeiling,
    estimatedFloor,
    expectedDurationMs,
    progressSource,
    realProgress,
    startedAt,
    status,
  ]);

  useEffect(() => {
    if (status === "failed" || status === "error") {
      cancelAnimationFrame(rafRef.current);
      return;
    }

    let lastFrame = performance.now();

    const tick = (now: number) => {
      const dt = Math.min((now - lastFrame) / 1000, 0.1);
      lastFrame = now;

      setDisplayed((current) => {
        const currentStatus = statusRef.current;

        if (isSuccess(currentStatus)) {
          const start = completionStartRef.current ?? now;
          const elapsed = now - start;
          const t = Math.min(elapsed / completionDuration, 1);
          const eased = 1 - (1 - t) * (1 - t);
          const base = completionBaseRef.current;
          return base + (100 - base) * eased;
        }

        if (isTerminal(currentStatus)) return current;

        if (progressSourceRef.current === "estimated") {
          const target = calculateEstimatedProgress({
            nowMs: Date.now(),
            startedAtMs: estimatedStartedAtRef.current ?? Date.now(),
            expectedDurationMs: expectedDurationMsRef.current,
            floor: estimatedFloorRef.current,
            ceiling: estimatedCeilingRef.current,
          });
          if (current >= target) return current;
          const gap = target - current;
          const step = (gap * chaseRate + 0.2) * dt * 60;
          return Math.min(current + step, target);
        }

        const target = targetRef.current;

        if (current >= target) {
          const ceiling = Math.min(target + creepAhead, creepCeiling);
          if (current >= ceiling) return current;
          return current + creepSpeed * dt;
        }

        const gap = target - current;
        const step = (gap * chaseRate + 0.3) * dt * 60;
        return Math.min(current + step, target);
      });

      rafRef.current = requestAnimationFrame(tick);
    };

    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [
    status,
    chaseRate,
    creepSpeed,
    creepCeiling,
    creepAhead,
    completionDuration,
  ]);

  if (status === "failed" || status === "error") return Math.round(displayed);
  if (isSuccess(status) && displayed >= 99.5) return 100;
  return Math.round(Math.max(0, Math.min(99, displayed)));
}
