"use client";

import { useCallback, useEffect, useState, useSyncExternalStore } from "react";

const STORAGE_PREFIX = "voxi.unread.v1";
const CHANGE_EVENT = "slackwsh:unread";

type Counts = Record<string, number>;

const EMPTY: Counts = Object.freeze({}) as Counts;

/**
 * `useSyncExternalStore` compares snapshots by identity, so parsing the JSON
 * on every read would re-render forever. Cache per workspace and only build a
 * new object when the serialized value actually changed.
 */
const snapshots = new Map<string, { raw: string; value: Counts }>();

function storageKey(workspaceId: string) {
  return `${STORAGE_PREFIX}:${workspaceId}`;
}

function readCounts(workspaceId: string): Counts {
  if (typeof window === "undefined" || !workspaceId) return EMPTY;
  const raw = window.localStorage.getItem(storageKey(workspaceId)) ?? "";
  const cached = snapshots.get(workspaceId);
  if (cached && cached.raw === raw) return cached.value;

  let value: Counts = EMPTY;
  if (raw) {
    try {
      const parsed = JSON.parse(raw) as Counts;
      if (parsed && typeof parsed === "object") value = parsed;
    } catch {
      value = EMPTY;
    }
  }
  snapshots.set(workspaceId, { raw, value });
  return value;
}

function writeCounts(workspaceId: string, counts: Counts) {
  if (typeof window === "undefined" || !workspaceId) return;
  const cleaned: Counts = {};
  for (const [id, n] of Object.entries(counts)) {
    if (n > 0) cleaned[id] = n;
  }
  const raw = JSON.stringify(cleaned);
  if (snapshots.get(workspaceId)?.raw === raw) return;

  window.localStorage.setItem(storageKey(workspaceId), raw);
  snapshots.set(workspaceId, { raw, value: cleaned });
  window.dispatchEvent(new CustomEvent(CHANGE_EVENT, { detail: { workspaceId, counts: cleaned } }));
}

export function getUnreadCounts(workspaceId: string): Counts {
  return readCounts(workspaceId);
}

export function getUnreadTotal(workspaceId: string): number {
  return Object.values(readCounts(workspaceId)).reduce((sum, n) => sum + n, 0);
}

export function getChannelUnread(workspaceId: string, channelId: string | number | null | undefined): number {
  if (channelId == null) return 0;
  return readCounts(workspaceId)[String(channelId)] ?? 0;
}

/** Replace the device cache with the authoritative counts returned by the API. */
export function hydrateUnreadCounts(
  workspaceId: string,
  conversations: Array<{ channel: { id: string | number }; member?: { unreadCount?: number } | null }>,
) {
  const counts: Counts = {};
  for (const row of conversations) {
    const count = row.member?.unreadCount ?? 0;
    if (count > 0) counts[String(row.channel.id)] = count;
  }
  writeCounts(workspaceId, counts);
}

/** Apply a server-pushed read update from another tab/device. */
export function setChannelUnread(
  workspaceId: string | number,
  channelId: string | number,
  count: number,
) {
  const ws = String(workspaceId);
  const ch = String(channelId);
  const counts = { ...readCounts(ws) };
  if (count > 0) counts[ch] = count;
  else delete counts[ch];
  writeCounts(ws, counts);
}

/** Bump unread for a channel (no-op if already viewing it in a visible tab). */
export function incrementUnread(
  workspaceId: string | number | null | undefined,
  channelId: string | number | null | undefined,
  by = 1,
): number {
  if (workspaceId == null || channelId == null) return 0;
  const ws = String(workspaceId);
  const ch = String(channelId);

  const openChannelId = new URLSearchParams(window.location.search).get("channelId");
  if (openChannelId && String(openChannelId) === ch && !document.hidden) return getChannelUnread(ws, ch);

  const counts = { ...readCounts(ws) };
  counts[ch] = (counts[ch] ?? 0) + Math.max(1, by);
  writeCounts(ws, counts);
  return counts[ch];
}

export function clearUnread(workspaceId: string | number | null | undefined, channelId: string | number | null | undefined) {
  if (workspaceId == null || channelId == null) return;
  const ws = String(workspaceId);
  const ch = String(channelId);
  const counts = { ...readCounts(ws) };
  if (!(ch in counts)) return;
  delete counts[ch];
  writeCounts(ws, counts);
}

export function shouldNotifyForChannel(channelId: string | number | null | undefined): boolean {
  if (channelId == null) return true;
  const openChannelId = new URLSearchParams(window.location.search).get("channelId");
  if (openChannelId && String(openChannelId) === String(channelId) && !document.hidden) return false;
  return true;
}

function subscribe(workspaceId: string, onStoreChange: () => void) {
  const handler = (event: Event) => {
    const detail = (event as CustomEvent<{ workspaceId?: string }>).detail;
    if (!detail?.workspaceId || detail.workspaceId === workspaceId) onStoreChange();
  };
  const onStorage = (event: StorageEvent) => {
    if (event.key === storageKey(workspaceId)) onStoreChange();
  };
  window.addEventListener(CHANGE_EVENT, handler);
  window.addEventListener("storage", onStorage);
  return () => {
    window.removeEventListener(CHANGE_EVENT, handler);
    window.removeEventListener("storage", onStorage);
  };
}

const emptySnapshot = () => EMPTY;

export function useUnreadCounts(workspaceId: string | null | undefined): Counts {
  const ws = workspaceId ?? "";
  const subscribeToWorkspace = useCallback(
    (onChange: () => void) => (ws ? subscribe(ws, onChange) : () => undefined),
    [ws],
  );
  const getSnapshot = useCallback(() => (ws ? readCounts(ws) : EMPTY), [ws]);
  return useSyncExternalStore(subscribeToWorkspace, getSnapshot, emptySnapshot);
}

export function useUnreadTotal(workspaceId: string | null | undefined): number {
  const counts = useUnreadCounts(workspaceId);
  return Object.values(counts).reduce((sum, n) => sum + n, 0);
}

/** Clear unread when the open channel changes or the tab becomes visible on it. */
export function useClearUnreadOnView(workspaceId: string | null | undefined, channelId: string | null | undefined) {
  const [visible, setVisible] = useState(true);

  useEffect(() => {
    const onVis = () => setVisible(!document.hidden);
    document.addEventListener("visibilitychange", onVis);
    return () => document.removeEventListener("visibilitychange", onVis);
  }, []);

  useEffect(() => {
    if (!workspaceId || !channelId || !visible) return;
    clearUnread(workspaceId, channelId);
    const total = getUnreadTotal(workspaceId);
    void import("../platform/adapter").then(({ createPlatformAdapter }) => {
      const platform = createPlatformAdapter();
      if (total > 0) void platform.badge.setBadge(total);
      else void platform.badge.clearBadge();
    });
  }, [workspaceId, channelId, visible]);
}
