import type { ProfileBrief } from '../friends/index.js';
import type { AppSupabaseClient } from '../supabase/client.js';
import type { ConversationSummary } from './types.js';

const PROFILE_BRIEF_COLS = 'user_id, username, display_name, avatar_url, banner_url';

function mapBrief(row: {
  user_id: string;
  username: string;
  display_name: string;
  avatar_url: string | null;
  banner_url: string | null;
}): ProfileBrief {
  return {
    userId: row.user_id,
    username: row.username,
    displayName: row.display_name,
    avatarUrl: row.avatar_url,
    bannerUrl: row.banner_url,
  };
}

async function currentUserId(client: AppSupabaseClient): Promise<string> {
  const { data, error } = await client.auth.getUser();
  if (error) throw error;
  if (!data.user) throw new Error('not authenticated');
  return data.user.id;
}

export async function listConversations(client: AppSupabaseClient): Promise<ConversationSummary[]> {
  const myId = await currentUserId(client);

  // 1. Caller's memberships. `archived` / `muted_until` live on the members
  // row (see migration 20260420000001). db-types snapshot predates them so
  // cast the select to bypass typing.
  const { data: myMembers, error: mErr } = await client
    .from('conversation_members')
    .select('conversation_id, role, accepted, archived, muted_until' as '*')
    .eq('user_id', myId);
  if (mErr) throw mErr;
  const myMembersList = (myMembers ?? []) as unknown as Array<{
    conversation_id: string;
    role: string;
    accepted: boolean;
    archived: boolean | null;
    muted_until: string | null;
  }>;
  if (myMembersList.length === 0) return [];

  const convIds = myMembersList.map((m) => m.conversation_id);

  // 2. Conversations
  const { data: convs, error: cErr } = await client
    .from('conversations')
    .select('id, type, name, avatar_url, created_at')
    .in('id', convIds);
  if (cErr) throw cErr;

  // 3. All members across these conversations
  const { data: allMembers, error: aErr } = await client
    .from('conversation_members')
    .select('conversation_id, user_id, role, accepted')
    .in('conversation_id', convIds);
  if (aErr) throw aErr;

  // 4. Profiles for ALL distinct member user ids (including self — the
  //    member list in GroupInfoPanel needs our own display name too).
  const memberIdsAll = Array.from(new Set((allMembers ?? []).map((m) => m.user_id)));
  const profileMap = new Map<string, ProfileBrief>();
  if (memberIdsAll.length > 0) {
    const { data: profiles, error: pErr } = await client
      .from('profiles')
      .select(PROFILE_BRIEF_COLS)
      .in('user_id', memberIdsAll);
    if (pErr) throw pErr;
    for (const p of profiles ?? []) {
      const b = mapBrief(p);
      profileMap.set(b.userId, b);
    }
  }

  // 5. Latest message per conversation
  const { data: latest, error: lErr } = await client
    .from('messages')
    .select('conversation_id, created_at')
    .in('conversation_id', convIds)
    .order('created_at', { ascending: false });
  if (lErr) throw lErr;
  const lastSeen = new Map<string, string>();
  for (const m of latest ?? []) {
    if (!lastSeen.has(m.conversation_id)) {
      lastSeen.set(m.conversation_id, m.created_at);
    }
  }

  const myMap = new Map(myMembersList.map((m) => [m.conversation_id, m]));
  const memberMap = new Map<string, typeof allMembers>();
  for (const m of allMembers ?? []) {
    const list = memberMap.get(m.conversation_id) ?? [];
    list.push(m);
    memberMap.set(m.conversation_id, list);
  }

  return (convs ?? []).map<ConversationSummary>((c) => {
    const mine = myMap.get(c.id);
    const members = (memberMap.get(c.id) ?? []).map((m) => ({
      userId: m.user_id,
      role: m.role,
      accepted: m.accepted,
      profile: profileMap.get(m.user_id) ?? null,
    }));
    const peer =
      c.type === 'dm'
        ? (members.find((m) => m.userId !== myId)?.profile ?? null)
        : null;
    const mineRow = mine as
      | { accepted: boolean; role: string; archived?: boolean; muted_until?: string | null }
      | undefined;
    return {
      id: c.id,
      type: c.type,
      name: c.name,
      avatarUrl: c.avatar_url,
      createdAt: c.created_at,
      peer,
      acceptedByMe: mineRow?.accepted ?? false,
      myRole: (mineRow?.role ?? 'member') as ConversationSummary['myRole'],
      members,
      lastMessageAt: lastSeen.get(c.id) ?? null,
      archived: mineRow?.archived ?? false,
      mutedUntil: mineRow?.muted_until ?? null,
    };
  });
}

// Toggle archive flag on the caller's own conversation_members row.
export async function setConversationArchived(
  client: AppSupabaseClient,
  conversationId: string,
  archived: boolean,
): Promise<void> {
  const myId = await currentUserId(client);
  const { error } = await client
    .from('conversation_members')
    .update({ archived } as never)
    .eq('conversation_id', conversationId)
    .eq('user_id', myId);
  if (error) throw error;
}

// Set mute until a specific ISO timestamp (null clears the mute). A
// far-future timestamp is the "muted forever" representation.
export async function setConversationMutedUntil(
  client: AppSupabaseClient,
  conversationId: string,
  until: string | null,
): Promise<void> {
  const myId = await currentUserId(client);
  const { error } = await client
    .from('conversation_members')
    .update({ muted_until: until } as never)
    .eq('conversation_id', conversationId)
    .eq('user_id', myId);
  if (error) throw error;
}

// Convenience: `null` unmutes, number means minutes from now. For "forever"
// pass a very large number (e.g. 100 years worth of minutes).
export function muteDurationToIso(minutes: number | null): string | null {
  if (minutes === null) return null;
  return new Date(Date.now() + minutes * 60 * 1000).toISOString();
}

// True iff the member is currently muted (mutedUntil present and > now).
export function isConversationMuted(mutedUntil: string | null): boolean {
  if (!mutedUntil) return false;
  return new Date(mutedUntil).getTime() > Date.now();
}
