import {
  ClientEvent,
  createClient,
  MemoryStore,
  SyncState,
  type MatrixClient,
  type Room,
} from 'matrix-js-sdk';
import { getCurrentSession, type Session } from '@cinny/app/state/sessions';

export type LiveConnectMode = 'mock' | 'live';

export type ManualSessionInput = {
  baseUrl: string;
  userId: string;
  deviceId: string;
  accessToken: string;
};

const MANUAL_SESSION_KEY = 'playgroundManualSession';

/** Session already used by Paarrot/Cinny on this origin (localStorage). */
export function peekPaarrotSession(): Session | undefined {
  return getCurrentSession();
}

export function loadManualSession(): ManualSessionInput | null {
  try {
    const raw = sessionStorage.getItem(MANUAL_SESSION_KEY);
    if (!raw) return null;
    const parsed = JSON.parse(raw) as ManualSessionInput;
    if (parsed?.baseUrl && parsed?.userId && parsed?.deviceId && parsed?.accessToken) {
      return parsed;
    }
  } catch {
    /* ignore */
  }
  return null;
}

export function saveManualSession(input: ManualSessionInput | null) {
  if (!input) {
    sessionStorage.removeItem(MANUAL_SESSION_KEY);
    return;
  }
  sessionStorage.setItem(MANUAL_SESSION_KEY, JSON.stringify(input));
}

function normalizeSession(input: Session | ManualSessionInput): Session {
  return {
    baseUrl: input.baseUrl.replace(/\/$/, ''),
    userId: input.userId,
    deviceId: input.deviceId,
    accessToken: input.accessToken,
  };
}

function resolveSession(explicit?: Session | ManualSessionInput): Session {
  if (explicit) return normalizeSession(explicit);

  const fromPaarrot = peekPaarrotSession();
  if (fromPaarrot) return normalizeSession(fromPaarrot);

  const manual = loadManualSession();
  if (manual) return normalizeSession(manual);

  throw new Error(
    'No Paarrot session found. Log in at / (same origin), or paste homeserver + token in the playground.'
  );
}

function waitForPrepared(mx: MatrixClient, timeoutMs = 90_000): Promise<void> {
  return new Promise((resolve, reject) => {
    const finish = (ok: boolean, err?: Error) => {
      clearTimeout(timer);
      mx.off(ClientEvent.Sync, onSync);
      if (ok) resolve();
      else reject(err ?? new Error('Sync failed'));
    };

    const timer = window.setTimeout(
      () => finish(false, new Error('Timed out waiting for Matrix sync')),
      timeoutMs
    );

    const onSync = (state: SyncState | null) => {
      if (state === SyncState.Prepared || state === SyncState.Syncing) {
        finish(true);
      }
    };

    mx.on(ClientEvent.Sync, onSync);
    const cur = mx.getSyncState();
    if (cur === SyncState.Prepared || cur === SyncState.Syncing) {
      finish(true);
    }
  });
}

/**
 * Live Matrix client for the playground.
 * Uses MemoryStore so it won't fight Paarrot's IndexedDB sync/crypto stores.
 * Skips Rust crypto — encrypted timelines won't decrypt, but profiles/rooms/settings APIs work.
 */
export async function connectLiveMatrixClient(
  session?: Session | ManualSessionInput
): Promise<MatrixClient> {
  const finalSession = resolveSession(session);

  const mx = createClient({
    baseUrl: finalSession.baseUrl,
    accessToken: finalSession.accessToken,
    userId: finalSession.userId,
    deviceId: finalSession.deviceId,
    store: new MemoryStore({ localStorage: globalThis.localStorage }),
    timelineSupport: true,
  });

  mx.setMaxListeners(50);
  await mx.startClient({
    lazyLoadMembers: true,
    initialSyncLimit: 20,
  });
  await waitForPrepared(mx);
  return mx;
}

export function stopLiveMatrixClient(mx: MatrixClient | null | undefined) {
  if (!mx) return;
  try {
    mx.stopClient();
  } catch {
    /* ignore */
  }
}

export function listJoinedRooms(mx: MatrixClient): Room[] {
  return mx
    .getRooms()
    .filter((r) => r.getMyMembership() === 'join')
    .sort((a, b) => (a.name || a.roomId).localeCompare(b.name || b.roomId));
}

export function pickDefaultRoom(mx: MatrixClient, preferredId?: string): Room | undefined {
  const rooms = listJoinedRooms(mx);
  if (preferredId) {
    const hit = rooms.find((r) => r.roomId === preferredId);
    if (hit) return hit;
  }
  return rooms[0];
}
