"use client";

import { useCallback, useEffect, useMemo, useState } from "react";
import { createClient, getBrowserUser } from "@/lib/supabase/client";
import { calculateEffectiveBilling } from "@/lib/organization-billing";
import type {
  Organization,
  OrganizationAuthPolicy,
  OrganizationBilling,
  OrganizationBillingAdjustment,
  OrganizationBillingEvent,
  OrganizationInvitation,
  OrganizationJoinRequest,
  OrganizationWithContext,
  OrganizationMember,
  OrganizationRole,
  OrganizationTeam,
  Profile,
  Team,
  TeamMember,
} from "@/types";

function slugify(value: string) {
  return value
    .toLowerCase()
    .trim()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, 40);
}

function createOrganizationSlug(name: string) {
  const base = slugify(name) || "organization";
  return `${base}-${Math.random().toString(36).slice(2, 8)}`;
}

type OrganizationMembersByOrg = Record<string, OrganizationMember[]>;
type OrganizationTeamsByOrg = Record<string, OrganizationTeam[]>;
type OrganizationAssignableByOrg = Record<string, Profile[]>;
type OrganizationBillingByOrg = Record<string, OrganizationBilling | null>;
type OrganizationBillingAdjustmentsByOrg = Record<string, OrganizationBillingAdjustment[]>;
type OrganizationBillingEventsByOrg = Record<string, OrganizationBillingEvent[]>;
type OrganizationAuthPolicyByOrg = Record<string, OrganizationAuthPolicy | null>;
type OrganizationInvitationsByOrg = Record<string, OrganizationInvitation[]>;
type OrganizationJoinRequestsByOrg = Record<string, OrganizationJoinRequest[]>;

export function useOrganizations() {
  const [organizations, setOrganizations] = useState<Organization[]>([]);
  const [membersByOrg, setMembersByOrg] = useState<OrganizationMembersByOrg>({});
  const [teamsByOrg, setTeamsByOrg] = useState<OrganizationTeamsByOrg>({});
  const [assignableProfilesByOrg, setAssignableProfilesByOrg] =
    useState<OrganizationAssignableByOrg>({});
  const [billingByOrg, setBillingByOrg] = useState<OrganizationBillingByOrg>({});
  const [billingAdjustmentsByOrg, setBillingAdjustmentsByOrg] =
    useState<OrganizationBillingAdjustmentsByOrg>({});
  const [billingEventsByOrg, setBillingEventsByOrg] =
    useState<OrganizationBillingEventsByOrg>({});
  const [authPolicyByOrg, setAuthPolicyByOrg] = useState<OrganizationAuthPolicyByOrg>({});
  const [invitationsByOrg, setInvitationsByOrg] = useState<OrganizationInvitationsByOrg>({});
  const [joinRequestsByOrg, setJoinRequestsByOrg] = useState<OrganizationJoinRequestsByOrg>({});
  const [loading, setLoading] = useState(true);
  const [userId, setUserId] = useState<string | null>(null);
  const [userEmail, setUserEmail] = useState<string | null>(null);
  const supabase = createClient();

  useEffect(() => {
    void getBrowserUser()
      .then((user) => {
        setUserId(user?.id ?? null);
        setUserEmail(user?.email?.toLowerCase() ?? null);
      })
      .catch(() => {
        setUserId(null);
        setUserEmail(null);
      });
  }, []);

  const fetchOrganizations = useCallback(async () => {
    if (!userId) {
      setOrganizations([]);
      setMembersByOrg({});
      setTeamsByOrg({});
      setAssignableProfilesByOrg({});
      setBillingByOrg({});
      setBillingAdjustmentsByOrg({});
      setBillingEventsByOrg({});
      setAuthPolicyByOrg({});
      setInvitationsByOrg({});
      setJoinRequestsByOrg({});
      setLoading(false);
      return;
    }

    setLoading(true);

    const { data: orgRows, error: orgError } = await supabase
      .from("organizations")
      .select("*")
      .order("name");

    if (orgError) throw orgError;

    const nextOrganizations = (orgRows || []) as Organization[];
    if (nextOrganizations.length === 0) {
      setOrganizations([]);
      setMembersByOrg({});
      setTeamsByOrg({});
      setAssignableProfilesByOrg({});
      setBillingByOrg({});
      setBillingAdjustmentsByOrg({});
      setBillingEventsByOrg({});
      setAuthPolicyByOrg({});
      setInvitationsByOrg({});
      setJoinRequestsByOrg({});
      setLoading(false);
      return;
    }

    const organizationIds = nextOrganizations.map((organization) => organization.id);

    const [
      memberRes,
      orgTeamsRes,
      billingRes,
      adjustmentsRes,
      billingEventsRes,
      authPolicyRes,
      invitationRes,
      joinRequestRes,
    ] =
      await Promise.all([
      supabase.from("organization_members").select("*").in("organization_id", organizationIds),
      supabase.from("organization_teams").select("*").in("organization_id", organizationIds),
      supabase.from("organization_billing").select("*").in("organization_id", organizationIds),
      supabase
        .from("organization_billing_adjustments")
        .select("*")
        .in("organization_id", organizationIds)
        .order("created_at", { ascending: false }),
      supabase
        .from("organization_billing_events")
        .select("*")
        .in("organization_id", organizationIds)
        .order("created_at", { ascending: false }),
      supabase.from("organization_auth_policies").select("*").in("organization_id", organizationIds),
      supabase
        .from("organization_invitations")
        .select("*")
        .in("organization_id", organizationIds)
        .order("created_at", { ascending: false }),
      supabase
        .from("organization_join_requests")
        .select("*")
        .in("organization_id", organizationIds)
        .order("requested_at", { ascending: false }),
    ]);

    if (memberRes.error) throw memberRes.error;
    if (orgTeamsRes.error) throw orgTeamsRes.error;
    if (billingRes.error) throw billingRes.error;
    if (adjustmentsRes.error) throw adjustmentsRes.error;
    if (billingEventsRes.error) throw billingEventsRes.error;
    if (authPolicyRes.error) throw authPolicyRes.error;
    if (invitationRes.error) throw invitationRes.error;
    if (joinRequestRes.error) throw joinRequestRes.error;

    const rawMembers = (memberRes.data || []) as OrganizationMember[];
    const rawOrgTeams = (orgTeamsRes.data || []) as OrganizationTeam[];
    const rawBilling = (billingRes.data || []) as OrganizationBilling[];
    const rawBillingAdjustments = (adjustmentsRes.data || []) as OrganizationBillingAdjustment[];
    const rawBillingEvents = (billingEventsRes.data || []) as OrganizationBillingEvent[];
    const rawAuthPolicies = (authPolicyRes.data || []) as OrganizationAuthPolicy[];
    const rawInvitations = (invitationRes.data || []) as OrganizationInvitation[];
    const rawJoinRequests = (joinRequestRes.data || []) as OrganizationJoinRequest[];
    const teamIds = Array.from(new Set(rawOrgTeams.map((row) => row.team_id)));

    let teamsMap = new Map<string, Team>();
    let teamMembers: TeamMember[] = [];
    if (teamIds.length > 0) {
      const [teamsRes, teamMembersRes] = await Promise.all([
        supabase.from("teams").select("*").in("id", teamIds),
        supabase.from("team_members").select("*").in("team_id", teamIds),
      ]);

      if (teamsRes.error) throw teamsRes.error;
      if (teamMembersRes.error) throw teamMembersRes.error;

      teamsMap = new Map(((teamsRes.data || []) as Team[]).map((team) => [team.id, team]));
      teamMembers = (teamMembersRes.data || []) as TeamMember[];
    }

    const profileIds = Array.from(
      new Set([
        ...nextOrganizations.map((organization) => organization.owner_id),
        ...rawMembers.map((member) => member.user_id),
        ...teamMembers.map((member) => member.user_id),
        ...rawBillingAdjustments.map((adjustment) => adjustment.created_by),
        ...rawBillingAdjustments.map((adjustment) => adjustment.revoked_by).filter(Boolean),
        ...rawBillingEvents.map((event) => event.actor_id).filter(Boolean),
      ])
    );

    let profileMap = new Map<string, Profile>();
    if (profileIds.length > 0) {
      const { data: profileRows, error: profileError } = await supabase
        .from("profiles")
        .select("id, email, full_name, avatar_url, created_at, updated_at")
        .in("id", profileIds);

      if (profileError) throw profileError;
      profileMap = new Map(
        ((profileRows || []) as Profile[]).map((profile) => [profile.id, profile])
      );
    }

    const nextMembersByOrg: OrganizationMembersByOrg = {};
    const nextTeamsByOrg: OrganizationTeamsByOrg = {};
    const nextAssignableProfilesByOrg: OrganizationAssignableByOrg = {};
    const nextBillingByOrg: OrganizationBillingByOrg = {};
    const nextBillingAdjustmentsByOrg: OrganizationBillingAdjustmentsByOrg = {};
    const nextBillingEventsByOrg: OrganizationBillingEventsByOrg = {};
    const nextAuthPolicyByOrg: OrganizationAuthPolicyByOrg = {};
    const nextInvitationsByOrg: OrganizationInvitationsByOrg = {};
    const nextJoinRequestsByOrg: OrganizationJoinRequestsByOrg = {};

    nextOrganizations.forEach((organization) => {
      const orgMembers = rawMembers.filter(
        (member) => member.organization_id === organization.id
      );

      if (!orgMembers.some((member) => member.user_id === organization.owner_id)) {
        orgMembers.unshift({
          organization_id: organization.id,
          user_id: organization.owner_id,
          role: "owner",
          title: "Organization owner",
          seat_status: "active",
          counts_toward_seat: true,
          joined_at: organization.created_at,
          profile: profileMap.get(organization.owner_id) || null,
        });
      }

      nextMembersByOrg[organization.id] = orgMembers.map((member) => ({
        ...member,
        profile: profileMap.get(member.user_id) || null,
      }));

      const orgTeams = rawOrgTeams
        .filter((row) => row.organization_id === organization.id)
        .map((row) => ({
          ...row,
          team: teamsMap.get(row.team_id) || null,
        }));

      nextTeamsByOrg[organization.id] = orgTeams;

      const assignableProfiles = Array.from(
        new Map(
          teamMembers
            .filter((member) => orgTeams.some((orgTeam) => orgTeam.team_id === member.team_id))
            .map((member) => [member.user_id, profileMap.get(member.user_id)])
            .filter((entry): entry is [string, Profile] => !!entry[1])
        ).values()
      );

      nextAssignableProfilesByOrg[organization.id] = assignableProfiles;
      nextBillingByOrg[organization.id] =
        rawBilling.find((billing) => billing.organization_id === organization.id) || null;
      nextBillingAdjustmentsByOrg[organization.id] = rawBillingAdjustments
        .filter((adjustment) => adjustment.organization_id === organization.id)
        .map((adjustment) => ({
          ...adjustment,
          actor: profileMap.get(adjustment.created_by) || null,
        }));
      nextBillingEventsByOrg[organization.id] = rawBillingEvents
        .filter((event) => event.organization_id === organization.id)
        .map((event) => ({
          ...event,
          actor: event.actor_id ? profileMap.get(event.actor_id) || null : null,
        }));
      nextAuthPolicyByOrg[organization.id] =
        rawAuthPolicies.find((policy) => policy.organization_id === organization.id) || null;
      nextInvitationsByOrg[organization.id] = rawInvitations.filter(
        (invitation) => invitation.organization_id === organization.id
      );
      nextJoinRequestsByOrg[organization.id] = rawJoinRequests.filter(
        (request) => request.organization_id === organization.id
      );
    });

    setOrganizations(nextOrganizations);
    setMembersByOrg(nextMembersByOrg);
    setTeamsByOrg(nextTeamsByOrg);
    setAssignableProfilesByOrg(nextAssignableProfilesByOrg);
    setBillingByOrg(nextBillingByOrg);
    setBillingAdjustmentsByOrg(nextBillingAdjustmentsByOrg);
    setBillingEventsByOrg(nextBillingEventsByOrg);
    setAuthPolicyByOrg(nextAuthPolicyByOrg);
    setInvitationsByOrg(nextInvitationsByOrg);
    setJoinRequestsByOrg(nextJoinRequestsByOrg);
    setLoading(false);
  }, [supabase, userId]);

  useEffect(() => {
    const timer = window.setTimeout(() => {
      void fetchOrganizations();
    }, 0);

    return () => window.clearTimeout(timer);
  }, [fetchOrganizations]);

  const organizationsWithContext = useMemo<OrganizationWithContext[]>(
    () =>
      organizations.map((organization) => ({
        ...organization,
        members: membersByOrg[organization.id] || [],
        teams: teamsByOrg[organization.id] || [],
        assignableProfiles: assignableProfilesByOrg[organization.id] || [],
        billing: billingByOrg[organization.id] || null,
        billingAdjustments: billingAdjustmentsByOrg[organization.id] || [],
        billingEvents: billingEventsByOrg[organization.id] || [],
        effectiveBilling: calculateEffectiveBilling(
          billingByOrg[organization.id] || null,
          billingAdjustmentsByOrg[organization.id] || []
        ),
        authPolicy: authPolicyByOrg[organization.id] || null,
        invitations: invitationsByOrg[organization.id] || [],
        joinRequests: joinRequestsByOrg[organization.id] || [],
      })),
    [
      assignableProfilesByOrg,
      authPolicyByOrg,
      billingByOrg,
      billingAdjustmentsByOrg,
      billingEventsByOrg,
      invitationsByOrg,
      joinRequestsByOrg,
      membersByOrg,
      organizations,
      teamsByOrg,
    ]
  );

  const incomingInvitations = useMemo(
    () =>
      Object.values(invitationsByOrg)
        .flat()
        .filter(
          (invitation) =>
            invitation.status === "pending" &&
            userEmail &&
            invitation.email.toLowerCase() === userEmail
        ),
    [invitationsByOrg, userEmail]
  );

  async function createOrganization(input: { name: string; description?: string }) {
    const user = await getBrowserUser({ force: true });
    if (!user) throw new Error("Not authenticated");
    if (!userId) setUserId(user.id);

    const { data, error } = await supabase
      .from("organizations")
      .insert({
        owner_id: user.id,
        name: input.name.trim(),
        slug: createOrganizationSlug(input.name),
        description: input.description?.trim() || "",
      })
      .select()
      .single();

    if (error) throw error;

    const { error: memberError } = await supabase.from("organization_members").insert({
      organization_id: data.id,
      user_id: user.id,
      role: "owner",
      title: "Organization owner",
      seat_status: "active",
      counts_toward_seat: true,
    });

    if (memberError) throw memberError;

    const { error: billingError } = await supabase.from("organization_billing").insert({
      organization_id: data.id,
      billing_email: user.email || "",
      billing_name: user.user_metadata?.full_name || input.name.trim(),
      plan: "starter",
      billing_cycle: "monthly",
      subscription_status: "trialing",
      included_seats: 5,
      purchased_seats: 0,
      monthly_price_cents: 2900,
      trial_ends_at: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString(),
      renews_at: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
    });

    if (billingError) throw billingError;

    const { error: authPolicyError } = await supabase
      .from("organization_auth_policies")
      .insert({
        organization_id: data.id,
      });

    if (authPolicyError) throw authPolicyError;

    await fetchOrganizations();
    return data as Organization;
  }

  async function updateOrganization(
    organizationId: string,
    input: Partial<{ name: string; description: string }>
  ) {
    const payload: Record<string, string> = {};
    if (typeof input.name === "string") payload.name = input.name.trim();
    if (typeof input.description === "string") {
      payload.description = input.description.trim();
    }

    const { error } = await supabase
      .from("organizations")
      .update(payload)
      .eq("id", organizationId);

    if (error) throw error;
    await fetchOrganizations();
  }

  async function addOrganizationMember(
    organizationId: string,
    profileId: string,
    role: Exclude<OrganizationRole, "owner">,
    title = ""
  ) {
    const { error } = await supabase.from("organization_members").upsert({
      organization_id: organizationId,
      user_id: profileId,
      role,
      title: title.trim(),
      seat_status: "active",
      counts_toward_seat: true,
    });

    if (error) throw error;
    await fetchOrganizations();
  }

  async function updateOrganizationMember(
    organizationId: string,
    profileId: string,
    input: Partial<{
      role: Exclude<OrganizationRole, "owner">;
      title: string;
      seat_status: OrganizationMember["seat_status"];
      counts_toward_seat: boolean;
    }>
  ) {
    const payload: Record<string, string | boolean> = {};
    if (typeof input.role === "string") payload.role = input.role;
    if (typeof input.title === "string") payload.title = input.title.trim();
    if (typeof input.seat_status === "string") payload.seat_status = input.seat_status;
    if (typeof input.counts_toward_seat === "boolean") {
      payload.counts_toward_seat = input.counts_toward_seat;
    }

    const { error } = await supabase
      .from("organization_members")
      .update(payload)
      .eq("organization_id", organizationId)
      .eq("user_id", profileId);

    if (error) throw error;
    await fetchOrganizations();
  }

  async function updateOrganizationBilling(
    organizationId: string,
    input: Partial<{
      billing_email: string;
      billing_name: string;
      plan: OrganizationBilling["plan"];
      billing_cycle: OrganizationBilling["billing_cycle"];
      subscription_status: OrganizationBilling["subscription_status"];
      included_seats: number;
      purchased_seats: number;
      monthly_price_cents: number;
      trial_ends_at: string | null;
      renews_at: string | null;
      cancel_at: string | null;
    }>
  ) {
    const payload: Record<string, string | number | null> = {};

    if (typeof input.billing_email === "string") payload.billing_email = input.billing_email.trim();
    if (typeof input.billing_name === "string") payload.billing_name = input.billing_name.trim();
    if (typeof input.plan === "string") payload.plan = input.plan;
    if (typeof input.billing_cycle === "string") payload.billing_cycle = input.billing_cycle;
    if (typeof input.subscription_status === "string") {
      payload.subscription_status = input.subscription_status;
    }
    if (typeof input.included_seats === "number") {
      payload.included_seats = Math.max(0, Math.round(input.included_seats));
    }
    if (typeof input.purchased_seats === "number") {
      payload.purchased_seats = Math.max(0, Math.round(input.purchased_seats));
    }
    if (typeof input.monthly_price_cents === "number") {
      payload.monthly_price_cents = Math.max(0, Math.round(input.monthly_price_cents));
    }
    if ("trial_ends_at" in input) payload.trial_ends_at = input.trial_ends_at ?? null;
    if ("renews_at" in input) payload.renews_at = input.renews_at ?? null;
    if ("cancel_at" in input) payload.cancel_at = input.cancel_at ?? null;

    const { error } = await supabase
      .from("organization_billing")
      .update(payload)
      .eq("organization_id", organizationId);

    if (error) throw error;
    await fetchOrganizations();
  }

  async function createOrganizationBillingAdjustment(
    organizationId: string,
    input: {
      type: OrganizationBillingAdjustment["type"];
      amount?: number | null;
      text_value?: string | null;
      note?: string;
      starts_at?: string;
      ends_at?: string | null;
      title?: string;
      details?: Record<string, unknown>;
    }
  ) {
    const currentUser = await getBrowserUser({ force: true });
    if (!currentUser) throw new Error("Not authenticated");

    const { error: adjustmentError } = await supabase
      .from("organization_billing_adjustments")
      .insert({
        organization_id: organizationId,
        created_by: currentUser.id,
        type: input.type,
        amount: typeof input.amount === "number" ? Math.round(input.amount) : null,
        text_value: input.text_value ?? null,
        note: input.note?.trim() || "",
        starts_at: input.starts_at || new Date().toISOString(),
        ends_at: input.ends_at ?? null,
        is_active: true,
      });

    if (adjustmentError) throw adjustmentError;

    const { error: eventError } = await supabase
      .from("organization_billing_events")
      .insert({
        organization_id: organizationId,
        actor_id: currentUser.id,
        action: "billing_adjustment_created",
        title: input.title || "Billing adjustment added",
        details: {
          type: input.type,
          amount: typeof input.amount === "number" ? Math.round(input.amount) : null,
          text_value: input.text_value ?? null,
          note: input.note?.trim() || "",
          starts_at: input.starts_at || new Date().toISOString(),
          ends_at: input.ends_at ?? null,
          ...(input.details || {}),
        },
      });

    if (eventError) throw eventError;
    await fetchOrganizations();
  }

  async function updateOrganizationAuthPolicy(
    organizationId: string,
    input: Partial<{
      allowed_auth_methods: OrganizationAuthPolicy["allowed_auth_methods"];
      allowed_email_domains: string[];
      auto_join_domains: string[];
      invite_only: boolean;
      require_domain_match: boolean;
      enforce_sso: boolean;
    }>
  ) {
    const payload: Record<string, string[] | boolean> = {};

    if (Array.isArray(input.allowed_auth_methods)) {
      payload.allowed_auth_methods = input.allowed_auth_methods;
    }
    if (Array.isArray(input.allowed_email_domains)) {
      payload.allowed_email_domains = input.allowed_email_domains;
    }
    if (Array.isArray(input.auto_join_domains)) {
      payload.auto_join_domains = input.auto_join_domains;
    }
    if (typeof input.invite_only === "boolean") payload.invite_only = input.invite_only;
    if (typeof input.require_domain_match === "boolean") {
      payload.require_domain_match = input.require_domain_match;
    }
    if (typeof input.enforce_sso === "boolean") payload.enforce_sso = input.enforce_sso;

    const { error } = await supabase
      .from("organization_auth_policies")
      .update(payload)
      .eq("organization_id", organizationId);

    if (error) throw error;
    await fetchOrganizations();
  }

  async function removeOrganizationMember(organizationId: string, profileId: string) {
    const { error } = await supabase
      .from("organization_members")
      .delete()
      .eq("organization_id", organizationId)
      .eq("user_id", profileId);

    if (error) throw error;
    await fetchOrganizations();
  }

  async function linkTeam(organizationId: string, teamId: string) {
    const { error } = await supabase
      .from("organization_teams")
      .upsert({ organization_id: organizationId, team_id: teamId });

    if (error) throw error;
    await fetchOrganizations();
  }

  async function unlinkTeam(organizationId: string, teamId: string) {
    const { error } = await supabase
      .from("organization_teams")
      .delete()
      .eq("organization_id", organizationId)
      .eq("team_id", teamId);

    if (error) throw error;
    await fetchOrganizations();
  }

  async function inviteOrganizationMember(
    organizationId: string,
    input: {
      email: string;
      role: Exclude<OrganizationRole, "owner">;
      message?: string;
    }
  ) {
    const currentUser = await getBrowserUser({ force: true });
    if (!currentUser) throw new Error("Not authenticated");

    const normalizedEmail = input.email.trim().toLowerCase();
    const existingMember = (membersByOrg[organizationId] || []).some(
      (member) => member.profile?.email?.toLowerCase() === normalizedEmail
    );

    if (existingMember) {
      throw new Error("This person is already part of the organization.");
    }

    const existingInvitation = (invitationsByOrg[organizationId] || []).some(
      (invitation) =>
        invitation.status === "pending" &&
        invitation.email.toLowerCase() === normalizedEmail
    );

    if (existingInvitation) {
      throw new Error("There is already a pending invitation for this email.");
    }

    const res = await fetch("/api/organizations/invitations", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        organizationId,
        email: normalizedEmail,
        role: input.role,
        message: input.message || "",
      }),
    });

    const result = await res.json();
    if (!res.ok) {
      throw new Error(result.error || "Invitation could not be sent.");
    }

    await fetchOrganizations();
    return result as {
      invitation: OrganizationInvitation;
      inviteUrl: string;
      delivery: "email_sent" | "stored_only";
    };
  }

  async function acceptOrganizationInvitation(invitationId: string) {
    const currentUser = await getBrowserUser({ force: true });
    if (!currentUser) throw new Error("Not authenticated");

    const invitation = incomingInvitations.find((item) => item.id === invitationId);
    if (!invitation) throw new Error("Invitation not found.");

    const { error: memberError } = await supabase.from("organization_members").insert({
      organization_id: invitation.organization_id,
      user_id: currentUser.id,
      role: invitation.role,
      title: "Invited member",
      seat_status: "active",
      counts_toward_seat: true,
    });

    if (memberError && memberError.code !== "23505") {
      throw memberError;
    }

    const { error: inviteError } = await supabase
      .from("organization_invitations")
      .update({ status: "accepted" })
      .eq("id", invitationId);

    if (inviteError) throw inviteError;
    await fetchOrganizations();
  }

  async function declineOrganizationInvitation(invitationId: string) {
    const currentUser = await getBrowserUser({ force: true });
    if (!currentUser) throw new Error("Not authenticated");

    const { error } = await supabase
      .from("organization_invitations")
      .update({ status: "declined" })
      .eq("id", invitationId);

    if (error) throw error;
    await fetchOrganizations();
  }

  async function revokeOrganizationInvitation(invitationId: string) {
    const currentUser = await getBrowserUser({ force: true });
    if (!currentUser) throw new Error("Not authenticated");

    const { error } = await supabase
      .from("organization_invitations")
      .update({ status: "revoked" })
      .eq("id", invitationId);

    if (error) throw error;
    await fetchOrganizations();
  }

  async function requestOrganizationJoin(input: { slug: string; message?: string }) {
    const currentUser = await getBrowserUser({ force: true });
    if (!currentUser) throw new Error("Not authenticated");

    const res = await fetch("/api/organizations/join-requests", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        slug: input.slug.trim().toLowerCase(),
        message: input.message || "",
      }),
    });

    const result = await res.json();
    if (!res.ok) {
      throw new Error(result.error || "Join request could not be created.");
    }

    await fetchOrganizations();
    return result as {
      request: OrganizationJoinRequest;
      organization: { id: string; name: string; slug: string };
    };
  }

  async function updateOrganizationJoinRequest(
    requestId: string,
    status: OrganizationJoinRequest["status"]
  ) {
    const currentUser = await getBrowserUser({ force: true });
    if (!currentUser) throw new Error("Not authenticated");

    const res = await fetch(`/api/organizations/join-requests/${requestId}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ status }),
    });

    const result = await res.json();
    if (!res.ok) {
      throw new Error(result.error || "Join request could not be updated.");
    }

    await fetchOrganizations();
    return result as { request: OrganizationJoinRequest };
  }

  return {
    organizations: organizationsWithContext,
    loading,
    userId,
    userEmail,
    incomingInvitations,
    createOrganization,
    updateOrganization,
    addOrganizationMember,
    updateOrganizationMember,
    updateOrganizationBilling,
    createOrganizationBillingAdjustment,
    updateOrganizationAuthPolicy,
    removeOrganizationMember,
    linkTeam,
    unlinkTeam,
    inviteOrganizationMember,
    acceptOrganizationInvitation,
    declineOrganizationInvitation,
    revokeOrganizationInvitation,
    requestOrganizationJoin,
    updateOrganizationJoinRequest,
    refetch: fetchOrganizations,
  };
}
