import { notify } from "@/lib/notifications/store";
import type { ResearchMap } from "@/lib/types/research";
import {
  listPersistedTopicIds,
  loadPersistedSession,
  removePersistedSession,
  savePersistedSession,
} from "./storage";
import type { PersistedRefineSession, RefineChatMessage, RefineSession, RefineTab } from "./types";

type Listener = () => void;
const listeners = new Set<Listener>();

const sessions = new Map<string, RefineSession>();
let activeTopicId: string | null = null;
let snapshotVersion = 0;

const applyCallbacks = new Map<string, (map: ResearchMap) => void>();
const inFlight = new Map<string, Promise<void>>();

let hydrated = false;

function cloneMap(map: ResearchMap): ResearchMap {
  return JSON.parse(JSON.stringify(map)) as ResearchMap;
}

function mapsEqual(a: ResearchMap, b: ResearchMap): boolean {
  return JSON.stringify(a) === JSON.stringify(b);
}

function emit() {
  snapshotVersion += 1;
  listeners.forEach((fn) => fn());
}

function toPersisted(session: RefineSession): PersistedRefineSession {
  return {
    topicId: session.topicId,
    topicLabel: session.topicLabel,
    draft: session.draft,
    baseline: session.baseline,
    messages: [],
    tab: session.tab,
    open: session.open,
    engaged: session.engaged,
    chatInput: "",
    updatedAt: session.updatedAt,
  };
}

function persistSession(session: RefineSession) {
  savePersistedSession(toPersisted(session));
}

function patchSession(topicId: string, patch: Partial<RefineSession>) {
  const current = sessions.get(topicId);
  if (!current) return;
  const next: RefineSession = {
    ...current,
    ...patch,
    updatedAt: new Date().toISOString(),
  };
  sessions.set(topicId, next);
  persistSession(next);
  emit();
}

/** 僅在 client mount 後呼叫，避免 SSR hydration 與 localStorage 不一致。 */
export function hydrateRefineSessionsFromStorage(): void {
  if (hydrated || typeof window === "undefined") return;
  hydrated = true;
  for (const topicId of listPersistedTopicIds()) {
    const persisted = loadPersistedSession(topicId);
    if (!persisted?.draft || !persisted.baseline) continue;
    const hasUnsavedDraft = !mapsEqual(persisted.draft, persisted.baseline);
    if (!hasUnsavedDraft) {
      removePersistedSession(topicId);
      continue;
    }
    sessions.set(topicId, {
      ...persisted,
      messages: [],
      chatInput: "",
      engaged: true,
      open: false,
      chatting: false,
      saving: false,
    });
  }
  emit();
}

export function subscribe(listener: Listener): () => void {
  listeners.add(listener);
  return () => listeners.delete(listener);
}

export function getSnapshotVersion(): number {
  return snapshotVersion;
}

export function getSession(topicId: string): RefineSession | null {
  return sessions.get(topicId) ?? null;
}

export function getActiveTopicId(): string | null {
  return activeTopicId;
}

export function getDisplayTopicId(): string | null {
  for (const [id, session] of sessions) {
    if (session.chatting) return id;
  }
  for (const [id, session] of sessions) {
    if (session.open) return id;
  }
  if (activeTopicId) {
    const active = sessions.get(activeTopicId);
    if (active?.engaged) return activeTopicId;
  }
  for (const [id, session] of sessions) {
    if (session.engaged) return id;
  }
  return null;
}

export function hasVisibleRefineSession(): boolean {
  for (const session of sessions.values()) {
    if (session.open || session.chatting) return true;
    if (
      session.engaged &&
      session.draft &&
      session.baseline &&
      !mapsEqual(session.draft, session.baseline)
    ) {
      return true;
    }
  }
  return false;
}

export function setActiveTopicId(topicId: string | null) {
  if (activeTopicId === topicId) return;
  activeTopicId = topicId;
  emit();
}

export function registerApplyCallback(
  topicId: string,
  callback: (map: ResearchMap) => void
): () => void {
  applyCallbacks.set(topicId, callback);
  return () => {
    if (applyCallbacks.get(topicId) === callback) {
      applyCallbacks.delete(topicId);
    }
  };
}

export function ensureSession(
  topicId: string,
  researchMap: ResearchMap,
  topicLabel: string
): RefineSession {
  const existing = sessions.get(topicId);
  if (existing) {
    if (existing.topicLabel !== topicLabel) {
      patchSession(topicId, { topicLabel });
    }
    return sessions.get(topicId)!;
  }

  const persisted = loadPersistedSession(topicId);
  if (persisted?.draft && persisted.baseline) {
    const hasUnsavedDraft = !mapsEqual(persisted.draft, persisted.baseline);
    if (hasUnsavedDraft) {
      const session: RefineSession = {
        ...persisted,
        topicLabel: persisted.topicLabel || topicLabel,
        messages: [],
        chatInput: "",
        engaged: true,
        open: false,
        chatting: false,
        saving: false,
      };
      sessions.set(topicId, session);
      emit();
      return session;
    }
    removePersistedSession(topicId);
  }

  const session: RefineSession = {
    topicId,
    topicLabel,
    draft: cloneMap(researchMap),
    baseline: cloneMap(researchMap),
    messages: [],
    tab: "chat",
    open: false,
    engaged: false,
    chatting: false,
    chatInput: "",
    saving: false,
    updatedAt: new Date().toISOString(),
  };
  sessions.set(topicId, session);
  persistSession(session);
  emit();
  return session;
}

export function syncResearchMap(
  topicId: string,
  researchMap: ResearchMap,
  topicLabel: string
) {
  const existing = sessions.get(topicId);
  if (!existing) return;

  const hasLocalChanges =
    existing.draft && existing.baseline
      ? !mapsEqual(existing.draft, existing.baseline)
      : false;

  if (hasLocalChanges || existing.chatting) {
    if (existing.topicLabel !== topicLabel) {
      patchSession(topicId, { topicLabel });
    }
    return;
  }

  patchSession(topicId, {
    topicLabel,
    draft: cloneMap(researchMap),
    baseline: cloneMap(researchMap),
    messages: [],
    chatInput: "",
  });
}

export function openRefine(
  topicId: string,
  opts?: { researchMap: ResearchMap; topicLabel: string }
) {
  if (opts) {
    const map = cloneMap(opts.researchMap);
    const existing = sessions.get(topicId);
    const session: RefineSession = {
      topicId,
      topicLabel: opts.topicLabel,
      draft: map,
      baseline: map,
      messages: [],
      tab: "chat",
      open: true,
      engaged: true,
      chatting: existing?.chatting ?? false,
      chatInput: "",
      saving: false,
      updatedAt: new Date().toISOString(),
    };
    sessions.set(topicId, session);
    persistSession(session);
    emit();
    return;
  }

  if (!sessions.has(topicId)) return;
  patchSession(topicId, { open: true, engaged: true });
}

export function setRefineOpen(topicId: string, open: boolean) {
  patchSession(topicId, { open });
}

export function setRefineTab(topicId: string, tab: RefineTab) {
  patchSession(topicId, { tab });
}

export function setChatInput(topicId: string, chatInput: string) {
  patchSession(topicId, { chatInput });
}

export function setDraft(topicId: string, draft: ResearchMap) {
  patchSession(topicId, { draft });
}

export function updateDraft(topicId: string, patch: Partial<ResearchMap>) {
  const session = sessions.get(topicId);
  if (!session?.draft) return;
  patchSession(topicId, { draft: { ...session.draft, ...patch } });
}

export function discardDraftChanges(topicId: string) {
  const session = sessions.get(topicId);
  if (!session?.baseline) return;
  patchSession(topicId, { draft: cloneMap(session.baseline) });
}

export function closeRefine(topicId: string, force = false) {
  const session = sessions.get(topicId);
  if (!session) return false;

  const hasChanges =
    session.draft && session.baseline
      ? !mapsEqual(session.draft, session.baseline)
      : false;

  if (hasChanges && !force) return false;

  clearRefineSession(topicId);
  return true;
}

export function clearRefineSession(topicId: string) {
  sessions.delete(topicId);
  removePersistedSession(topicId);
  emit();
}

async function runRefineChat(topicId: string, userMsg: string, history: RefineChatMessage[]) {
  const session = sessions.get(topicId);
  if (!session?.draft) return;

  const wasOpen = session.open;
  const label = session.topicLabel;

  try {
    const res = await fetch("/api/refine-research-map", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        topicId,
        researchMap: session.draft,
        message: userMsg,
        history,
      }),
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.error ?? "AI 微調失敗");

    const current = sessions.get(topicId);
    if (!current) return;

    patchSession(topicId, {
      draft: data.researchMap as ResearchMap,
      messages: [...current.messages, { role: "assistant", content: data.reply }],
      chatting: false,
    });

    const after = sessions.get(topicId);
    if (!after) return;

    const shouldNotify = !wasOpen || activeTopicId !== topicId;
    if (shouldNotify) {
      notify({
        type: "success",
        title: "AI 微調完成",
        message: label,
        href: `/scans/${topicId}`,
      });
    }
  } catch (err) {
    const msg = err instanceof Error ? err.message : "AI 微調失敗";
    const current = sessions.get(topicId);
    if (!current) return;

    patchSession(topicId, {
      messages: [...current.messages, { role: "assistant", content: `⚠️ ${msg}` }],
      chatting: false,
    });

    notify({
      type: "error",
      title: "AI 微調失敗",
      message: msg,
      href: `/scans/${topicId}`,
    });
  } finally {
    inFlight.delete(topicId);
  }
}

export function sendRefineChat(topicId: string, userMsg: string) {
  const session = sessions.get(topicId);
  if (!session?.draft || !userMsg.trim() || session.chatting) return;

  const trimmed = userMsg.trim();
  const history = session.messages;

  patchSession(topicId, {
    chatInput: "",
    messages: [...session.messages, { role: "user", content: trimmed }],
    chatting: true,
    engaged: true,
  });

  const promise = runRefineChat(topicId, trimmed, history);
  inFlight.set(topicId, promise);
}

export function isRefineChatting(topicId: string): boolean {
  return sessions.get(topicId)?.chatting ?? false;
}

export async function saveRefineSession(topicId: string): Promise<boolean> {
  const session = sessions.get(topicId);
  if (!session?.draft || !session.baseline) return false;

  if (mapsEqual(session.draft, session.baseline)) return false;

  patchSession(topicId, { saving: true });

  try {
    const res = await fetch("/api/topics", {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ id: topicId, researchMap: session.draft }),
    });
    const data = await res.json();
    if (!res.ok) {
      notify({ type: "error", title: "儲存失敗", message: data.error });
      patchSession(topicId, { saving: false });
      return false;
    }

    const saved = cloneMap(session.draft);
    patchSession(topicId, {
      baseline: saved,
      draft: saved,
      saving: false,
      messages: [],
      chatInput: "",
    });

    applyCallbacks.get(topicId)?.(saved);
    notify({ type: "success", title: "研究地圖已更新", message: session.topicLabel });
    return true;
  } catch (err) {
    const msg = err instanceof Error ? err.message : "儲存失敗";
    notify({ type: "error", title: "儲存失敗", message: msg });
    patchSession(topicId, { saving: false });
    return false;
  }
}

export function sessionHasChanges(topicId: string): boolean {
  const session = sessions.get(topicId);
  if (!session?.draft || !session.baseline) return false;
  return !mapsEqual(session.draft, session.baseline);
}