import { encodeBlurHash, validBlurHash } from '../utils/blurHash';
import { fitMediaSize } from '../utils/common';

const STORAGE_KEY = 'paarrot.media.meta';
const LEGACY_DIMENSIONS_KEY = 'paarrot.media.dimensions';
const MAX_ENTRIES = 500;

/** Same max box as MImage / MVideo attachment rendering. */
export const ATTACHMENT_MAX_SIZE = 400;

export type MediaMeta = {
  w: number;
  h: number;
  blurHash?: string;
};

export type MediaDimensions = Pick<MediaMeta, 'w' | 'h'>;

type CacheMap = Record<string, MediaMeta>;

let memoryCache: CacheMap | null = null;

const loadCache = (): CacheMap => {
  if (memoryCache) return memoryCache;
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (raw) {
      memoryCache = JSON.parse(raw) as CacheMap;
      return memoryCache;
    }
    // Migrate older dimensions-only cache once.
    const legacy = localStorage.getItem(LEGACY_DIMENSIONS_KEY);
    if (legacy) {
      memoryCache = JSON.parse(legacy) as CacheMap;
      localStorage.setItem(STORAGE_KEY, legacy);
      localStorage.removeItem(LEGACY_DIMENSIONS_KEY);
      return memoryCache;
    }
  } catch {
    // ignore
  }
  memoryCache = {};
  return memoryCache;
};

const persistCache = (cache: CacheMap) => {
  memoryCache = cache;
  try {
    const keys = Object.keys(cache);
    if (keys.length > MAX_ENTRIES) {
      const trimmed: CacheMap = {};
      keys.slice(keys.length - MAX_ENTRIES).forEach((key) => {
        trimmed[key] = cache[key];
      });
      memoryCache = trimmed;
      localStorage.setItem(STORAGE_KEY, JSON.stringify(trimmed));
      return;
    }
    localStorage.setItem(STORAGE_KEY, JSON.stringify(cache));
  } catch {
    // Quota / private mode — keep in-memory only.
  }
};

const touchEntry = (mxcUrl: string, patch: Partial<MediaMeta>): void => {
  if (!mxcUrl) return;
  const cache = loadCache();
  const prev = cache[mxcUrl];
  const next: MediaMeta = {
    w: patch.w ?? prev?.w ?? 0,
    h: patch.h ?? prev?.h ?? 0,
    blurHash: patch.blurHash ?? prev?.blurHash,
  };
  if (next.w <= 0 || next.h <= 0) return;
  if (
    prev &&
    prev.w === next.w &&
    prev.h === next.h &&
    prev.blurHash === next.blurHash
  ) {
    return;
  }
  if (prev) delete cache[mxcUrl];
  cache[mxcUrl] = next;
  persistCache(cache);
};

export const getMediaDimensions = (mxcUrl: string): MediaDimensions | undefined => {
  if (!mxcUrl) return undefined;
  const entry = loadCache()[mxcUrl];
  if (!entry || entry.w <= 0 || entry.h <= 0) return undefined;
  return { w: entry.w, h: entry.h };
};

/** Fit attachment box size using event info, then local dimension cache. */
export const resolveAttachmentBoxSize = (
  mxcUrl: string | undefined,
  w?: number,
  h?: number,
  maxSize: number = ATTACHMENT_MAX_SIZE
): { width: number; height: number } => {
  const cached = mxcUrl && (!w || !h) ? getMediaDimensions(mxcUrl) : undefined;
  const naturalW = w && w > 0 ? w : cached?.w;
  const naturalH = h && h > 0 ? h : cached?.h;
  return fitMediaSize(naturalW ?? 0, naturalH ?? 0, maxSize, maxSize);
};

export const setMediaDimensions = (mxcUrl: string, w: number, h: number): void => {
  if (!mxcUrl || w <= 0 || h <= 0) return;
  touchEntry(mxcUrl, { w, h });
};

export const getMediaBlurHash = (mxcUrl: string): string | undefined => {
  if (!mxcUrl) return undefined;
  return validBlurHash(loadCache()[mxcUrl]?.blurHash);
};

export const setMediaBlurHash = (mxcUrl: string, blurHash: string, w?: number, h?: number): void => {
  const hash = validBlurHash(blurHash);
  if (!mxcUrl || !hash) return;
  const cache = loadCache();
  const prev = cache[mxcUrl];
  const nextW = w && w > 0 ? w : prev?.w;
  const nextH = h && h > 0 ? h : prev?.h;
  if (!nextW || !nextH) {
    // Dimensions required for the shared meta entry; skip until we know size.
    return;
  }
  touchEntry(mxcUrl, { w: nextW, h: nextH, blurHash: hash });
};

/** Encode + cache a blurhash from a loaded image/video (idle, non-blocking). */
export const rememberMediaBlurHash = (
  mxcUrl: string,
  media: HTMLImageElement | HTMLVideoElement
): void => {
  if (!mxcUrl || typeof window === 'undefined') return;

  const run = () => {
    try {
      const w =
        media instanceof HTMLVideoElement
          ? media.videoWidth
          : media.naturalWidth || media.width;
      const h =
        media instanceof HTMLVideoElement
          ? media.videoHeight
          : media.naturalHeight || media.height;
      if (w <= 0 || h <= 0) return;

      setMediaDimensions(mxcUrl, w, h);
      if (getMediaBlurHash(mxcUrl)) return;

      const encW = 32;
      const encH = Math.max(1, Math.round(encW * (h / w)));
      const hash = encodeBlurHash(media, encW, encH);
      if (hash) setMediaBlurHash(mxcUrl, hash, w, h);
    } catch {
      // Encoding can fail for tainted canvases; ignore.
    }
  };

  if (typeof window.requestIdleCallback === 'function') {
    window.requestIdleCallback(() => run(), { timeout: 1500 });
  } else {
    window.setTimeout(run, 0);
  }
};
