"use client";

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

import type { ChatMediaKind } from "@/data/dto/chat";
import { BrowserDetector, Logger, PlatformDetector, Result } from "@/utils";

import {
  cacheRemoteChatMediaBlob,
  resolveCachedChatMediaBlob,
} from "./chat_media_cache_client";
import { isCacheableRemoteChatMediaUrl } from "./chat_media_url";

const log = new Logger("LibChatUseCachedChatMediaUrl");

export interface UseCachedChatMediaUrlInput {
  messageId?: string | null;
  remoteUrl?: string | null;
  kind: ChatMediaKind;
}

export interface UseCachedChatMediaUrlOutput {
  mediaUrl: string;
  isUsingCachedMedia: boolean;
  reportMediaError: () => void;
}

interface CachedMediaUrlState {
  key: string;
  mediaUrl: string;
  objectUrl: string | null;
  source: "data-url" | "object-url";
}

export function useCachedChatMediaUrl({
  messageId,
  remoteUrl,
  kind,
}: UseCachedChatMediaUrlInput): UseCachedChatMediaUrlOutput {
  const [cachedState, setCachedState] = useState<CachedMediaUrlState | null>(
    null,
  );
  const cachedStateRef = useRef<CachedMediaUrlState | null>(null);
  const [failedCacheKey, setFailedCacheKey] = useState<string | null>(null);
  const fallbackUrl = remoteUrl ?? "";
  const stateKey =
    messageId && remoteUrl ? `${messageId}:${kind}:${remoteUrl}` : "";

  useEffect(() => {
    if (
      !messageId ||
      !remoteUrl ||
      !isCacheableRemoteChatMediaUrl(remoteUrl) ||
      failedCacheKey === stateKey
    ) {
      return;
    }

    let cancelled = false;

    const applyBlob = async (blob: Blob) => {
      const nextUrl = await createCachedMediaUrl(blob);
      if (cancelled) {
        revokeCachedMediaUrl(nextUrl);
        return;
      }
      setCachedState((previous) => {
        if (previous) revokeCachedMediaUrl(previous);
        const nextState = {
          key: stateKey,
          ...nextUrl,
        };
        cachedStateRef.current = nextState;
        return nextState;
      });
    };

    const resolveCachedMedia = async () => {
      const cachedResult = await resolveCachedChatMediaBlob({
        messageId,
        kind,
      });
      if (cancelled) return;

      if (Result.isOk(cachedResult) && cachedResult.data) {
        await applyBlob(cachedResult.data);
        return;
      }

      const cacheResult = await cacheRemoteChatMediaBlob({
        messageId,
        kind,
        remoteUrl,
      });
      if (cancelled) return;

      if (Result.isOk(cacheResult)) {
        await applyBlob(cacheResult.data);
      }
    };

    void resolveCachedMedia().catch((error) => {
      log.warn("[chat-media] cached media url resolve failed", {
        messageId,
        kind,
        remoteUrl,
        error,
      });
    });

    return () => {
      cancelled = true;
    };
  }, [failedCacheKey, kind, messageId, remoteUrl, stateKey]);

  useEffect(() => {
    return () => {
      if (cachedStateRef.current) revokeCachedMediaUrl(cachedStateRef.current);
      cachedStateRef.current = null;
    };
  }, []);

  const reportMediaError = () => {
    if (cachedState?.key !== stateKey) return;
    log.warn("[chat-media] cached media url failed, fallback to remote url", {
      messageId,
      kind,
      remoteUrl,
      source: cachedState.source,
    });
    setFailedCacheKey(stateKey);
    setCachedState((previous) => {
      if (previous) revokeCachedMediaUrl(previous);
      cachedStateRef.current = null;
      return null;
    });
  };

  if (cachedState?.key === stateKey) {
    return {
      mediaUrl: cachedState.mediaUrl,
      isUsingCachedMedia: true,
      reportMediaError,
    };
  }

  return {
    mediaUrl: fallbackUrl,
    isUsingCachedMedia: false,
    reportMediaError,
  };
}

async function createCachedMediaUrl(
  blob: Blob,
): Promise<Omit<CachedMediaUrlState, "key">> {
  if (shouldUseDataUrlForCachedMedia()) {
    return {
      mediaUrl: await blobToDataUrl(blob),
      objectUrl: null,
      source: "data-url",
    };
  }

  const objectUrl = URL.createObjectURL(blob);
  return {
    mediaUrl: objectUrl,
    objectUrl,
    source: "object-url",
  };
}

function revokeCachedMediaUrl(
  state: Pick<CachedMediaUrlState, "objectUrl">,
): void {
  if (state.objectUrl) URL.revokeObjectURL(state.objectUrl);
}

function shouldUseDataUrlForCachedMedia(): boolean {
  return PlatformDetector.isIOS() && BrowserDetector.isSafari();
}

function blobToDataUrl(blob: Blob): Promise<string> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.addEventListener("load", () => {
      if (typeof reader.result === "string") {
        resolve(reader.result);
        return;
      }
      reject(new Error("Media blob could not be converted to a data URL."));
    });
    reader.addEventListener("error", () => {
      reject(reader.error ?? new Error("Media blob data URL read failed."));
    });
    reader.readAsDataURL(blob);
  });
}
