-- =============================================================================
-- AgenciaPsi — Functions — Core (profiles, tenants, membros, auth)
-- =============================================================================
-- handle_new_user, ensure_personal_tenant, whoami, my_tenants,
-- tenant_add_member, tenant_remove_member, tenant_invite, guards, etc.
-- =============================================================================

CREATE FUNCTION public.__rls_ping() RETURNS text
    LANGUAGE sql STABLE
    AS $$
  select 'ok'::text;
$$;


ALTER FUNCTION public.__rls_ping() OWNER TO supabase_admin;

SET default_tablespace = '';

SET default_table_access_method = heap;

--
-- Name: subscriptions; Type: TABLE; Schema: public; Owner: supabase_admin
--


CREATE FUNCTION public.ensure_personal_tenant() RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
DECLARE
  v_uid      uuid;
  v_existing uuid;
BEGIN
  v_uid := auth.uid();
  IF v_uid IS NULL THEN
    RAISE EXCEPTION 'Not authenticated';
  END IF;

  SELECT tm.tenant_id INTO v_existing
  FROM public.tenant_members tm
  JOIN public.tenants t ON t.id = tm.tenant_id
  WHERE tm.user_id = v_uid
    AND tm.status = 'active'
    AND t.kind IN ('therapist', 'saas')
  ORDER BY tm.created_at DESC
  LIMIT 1;

  IF v_existing IS NOT NULL THEN
    RETURN v_existing;
  END IF;

  RETURN public.provision_account_tenant(v_uid, 'therapist');
END;
$$;


ALTER FUNCTION public.ensure_personal_tenant() OWNER TO supabase_admin;

--
-- Name: ensure_personal_tenant_for_user(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.ensure_personal_tenant_for_user(p_user_id uuid) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  v_uid uuid;
  v_existing uuid;
  v_tenant uuid;
  v_email text;
  v_name text;
begin
  v_uid := p_user_id;
  if v_uid is null then
    raise exception 'Missing user id';
  end if;

  -- só considera tenant pessoal (kind='saas')
  select tm.tenant_id
    into v_existing
  from public.tenant_members tm
  join public.tenants t on t.id = tm.tenant_id
  where tm.user_id = v_uid
    and tm.status = 'active'
    and t.kind = 'saas'
  order by tm.created_at desc
  limit 1;

  if v_existing is not null then
    return v_existing;
  end if;

  select email into v_email
  from auth.users
  where id = v_uid;

  v_name := coalesce(split_part(v_email, '@', 1), 'Conta');

  insert into public.tenants (name, kind, created_at)
  values (v_name || ' (Pessoal)', 'saas', now())
  returning id into v_tenant;

  insert into public.tenant_members (tenant_id, user_id, role, status, created_at)
  values (v_tenant, v_uid, 'tenant_admin', 'active', now());

  return v_tenant;
end;
$$;


ALTER FUNCTION public.ensure_personal_tenant_for_user(p_user_id uuid) OWNER TO supabase_admin;

--
-- Name: faq_votar(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.faq_votar(faq_id uuid) RETURNS void
CREATE FUNCTION public.ensure_personal_tenant_for_user(p_user_id uuid) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
declare
  v_uid uuid;
  v_existing uuid;
  v_tenant uuid;
  v_email text;
  v_name text;
begin
  v_uid := p_user_id;
  if v_uid is null then
    raise exception 'Missing user id';
  end if;

  -- só considera tenant pessoal (kind='saas')
  select tm.tenant_id
    into v_existing
  from public.tenant_members tm
  join public.tenants t on t.id = tm.tenant_id
  where tm.user_id = v_uid
    and tm.status = 'active'
    and t.kind = 'saas'
  order by tm.created_at desc
  limit 1;

  if v_existing is not null then
    return v_existing;
  end if;

  select email into v_email
  from auth.users
  where id = v_uid;

  v_name := coalesce(split_part(v_email, '@', 1), 'Conta');

  insert into public.tenants (name, kind, created_at)
  values (v_name || ' (Pessoal)', 'saas', now())
  returning id into v_tenant;

  insert into public.tenant_members (tenant_id, user_id, role, status, created_at)
  values (v_tenant, v_uid, 'tenant_admin', 'active', now());

  return v_tenant;
end;
$$;


ALTER FUNCTION public.ensure_personal_tenant_for_user(p_user_id uuid) OWNER TO supabase_admin;

--
-- Name: faq_votar(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.faq_votar(faq_id uuid) RETURNS void
CREATE FUNCTION public.get_my_email() RETURNS text
    LANGUAGE sql SECURITY DEFINER
    SET search_path TO 'public', 'auth'
    AS $$
  select lower(email)
  from auth.users
  where id = auth.uid();
$$;


ALTER FUNCTION public.get_my_email() OWNER TO supabase_admin;

--
-- Name: guard_account_type_immutable(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.guard_account_type_immutable() RETURNS trigger
CREATE FUNCTION public.guard_account_type_immutable() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  IF OLD.account_type <> 'free' AND NEW.account_type IS DISTINCT FROM OLD.account_type THEN
    RAISE EXCEPTION 'account_type é imutável após escolha (atual: "%" para tentativa: "%"). Para mudar de perfil, crie uma nova conta.', OLD.account_type, NEW.account_type
      USING ERRCODE = 'P0001';
  END IF;
  RETURN NEW;
END;
$$;


ALTER FUNCTION public.guard_account_type_immutable() OWNER TO supabase_admin;

--
-- Name: guard_locked_commitment(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.guard_locked_commitment() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
  if (old.is_locked = true) then
    if (tg_op = 'DELETE') then
      raise exception 'Compromisso bloqueado não pode ser excluído.';
    end if;

    if (tg_op = 'UPDATE') then
      if (new.active = false) then
        raise exception 'Compromisso bloqueado não pode ser desativado.';
      end if;

      -- trava renomear (mantém o "Sessão" sempre igual)
      if (new.name is distinct from old.name) then
        raise exception 'Compromisso bloqueado não pode ser renomeado.';
      end if;

      -- se quiser travar descrição também, descomente:
      -- if (new.description is distinct from old.description) then
      --   raise exception 'Compromisso bloqueado não pode alterar descrição.';
      -- end if;
    end if;
  end if;

  return new;
end;
$$;


ALTER FUNCTION public.guard_locked_commitment() OWNER TO supabase_admin;

--
-- Name: guard_no_change_core_plan_key(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.guard_no_change_core_plan_key() RETURNS trigger
CREATE FUNCTION public.guard_patient_cannot_own_tenant() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
DECLARE
  v_account_type text;
BEGIN
  SELECT account_type INTO v_account_type
  FROM public.profiles
  WHERE id = NEW.user_id;

  IF v_account_type = 'patient' AND NEW.role IN ('tenant_admin', 'therapist') THEN
    RAISE EXCEPTION 'Usuário com perfil "patient" não pode ser proprietário ou terapeuta de um tenant. Se tornou profissional? Crie uma nova conta.'
      USING ERRCODE = 'P0001';
  END IF;

  RETURN NEW;
END;
$$;


ALTER FUNCTION public.guard_patient_cannot_own_tenant() OWNER TO supabase_admin;

--
-- Name: guard_tenant_kind_immutable(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.guard_tenant_kind_immutable() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  IF NEW.kind IS DISTINCT FROM OLD.kind THEN
    RAISE EXCEPTION 'tenants.kind é imutável após criação. Tentativa de alterar "%" para "%".', OLD.kind, NEW.kind
      USING ERRCODE = 'P0001';
  END IF;
  RETURN NEW;
END;
$$;


ALTER FUNCTION public.guard_tenant_kind_immutable() OWNER TO supabase_admin;

--
-- Name: handle_new_user(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.handle_new_user() RETURNS trigger
CREATE FUNCTION public.guard_tenant_kind_immutable() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  IF NEW.kind IS DISTINCT FROM OLD.kind THEN
    RAISE EXCEPTION 'tenants.kind é imutável após criação. Tentativa de alterar "%" para "%".', OLD.kind, NEW.kind
      USING ERRCODE = 'P0001';
  END IF;
  RETURN NEW;
END;
$$;


ALTER FUNCTION public.guard_tenant_kind_immutable() OWNER TO supabase_admin;

--
-- Name: handle_new_user(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.handle_new_user() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
BEGIN
  INSERT INTO public.profiles (id, role, account_type)
  VALUES (NEW.id, 'portal_user', 'free')
  ON CONFLICT (id) DO NOTHING;
  RETURN NEW;
END;
$$;


ALTER FUNCTION public.handle_new_user() OWNER TO supabase_admin;

--
-- Name: handle_new_user_create_personal_tenant(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.handle_new_user_create_personal_tenant() RETURNS trigger
CREATE FUNCTION public.handle_new_user() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
BEGIN
  INSERT INTO public.profiles (id, role, account_type)
  VALUES (NEW.id, 'portal_user', 'free')
  ON CONFLICT (id) DO NOTHING;
  RETURN NEW;
END;
$$;


ALTER FUNCTION public.handle_new_user() OWNER TO supabase_admin;

--
-- Name: handle_new_user_create_personal_tenant(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.handle_new_user_create_personal_tenant() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
BEGIN
  -- Desabilitado. Tenant criado no onboarding via provision_account_tenant().
  RETURN NEW;
END;
$$;


ALTER FUNCTION public.handle_new_user_create_personal_tenant() OWNER TO supabase_admin;

--
-- Name: has_feature(uuid, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.has_feature(p_owner_id uuid, p_feature_key text) RETURNS boolean
CREATE FUNCTION public.handle_new_user_create_personal_tenant() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
BEGIN
  -- Desabilitado. Tenant criado no onboarding via provision_account_tenant().
  RETURN NEW;
END;
$$;


ALTER FUNCTION public.handle_new_user_create_personal_tenant() OWNER TO supabase_admin;

--
-- Name: has_feature(uuid, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.has_feature(p_owner_id uuid, p_feature_key text) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select exists (
    select 1
    from public.owner_feature_entitlements e
    where e.owner_id = p_owner_id
      and e.feature_key = p_feature_key
  );
$$;


ALTER FUNCTION public.has_feature(p_owner_id uuid, p_feature_key text) OWNER TO supabase_admin;

--
-- Name: is_clinic_tenant(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.is_clinic_tenant(_tenant_id uuid) RETURNS boolean
CREATE FUNCTION public.has_feature(p_owner_id uuid, p_feature_key text) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select exists (
    select 1
    from public.owner_feature_entitlements e
    where e.owner_id = p_owner_id
      and e.feature_key = p_feature_key
  );
$$;


ALTER FUNCTION public.has_feature(p_owner_id uuid, p_feature_key text) OWNER TO supabase_admin;

--
-- Name: is_clinic_tenant(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.is_clinic_tenant(_tenant_id uuid) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  SELECT EXISTS (
    SELECT 1 FROM public.tenants t
    WHERE t.id = _tenant_id
      AND t.kind IN ('clinic', 'clinic_coworking', 'clinic_reception', 'clinic_full')
  );
$$;


ALTER FUNCTION public.is_clinic_tenant(_tenant_id uuid) OWNER TO supabase_admin;

--
-- Name: is_saas_admin(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.is_saas_admin() RETURNS boolean
CREATE FUNCTION public.is_clinic_tenant(_tenant_id uuid) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  SELECT EXISTS (
    SELECT 1 FROM public.tenants t
    WHERE t.id = _tenant_id
      AND t.kind IN ('clinic', 'clinic_coworking', 'clinic_reception', 'clinic_full')
  );
$$;


ALTER FUNCTION public.is_clinic_tenant(_tenant_id uuid) OWNER TO supabase_admin;

--
-- Name: is_saas_admin(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.is_saas_admin() RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select exists (
    select 1 from public.saas_admins sa
    where sa.user_id = auth.uid()
  );
$$;


ALTER FUNCTION public.is_saas_admin() OWNER TO supabase_admin;

--
-- Name: is_tenant_admin(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.is_tenant_admin(p_tenant_id uuid) RETURNS boolean
CREATE FUNCTION public.is_saas_admin() RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select exists (
    select 1 from public.saas_admins sa
    where sa.user_id = auth.uid()
  );
$$;


ALTER FUNCTION public.is_saas_admin() OWNER TO supabase_admin;

--
-- Name: is_tenant_admin(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.is_tenant_admin(p_tenant_id uuid) RETURNS boolean
    LANGUAGE sql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
  select exists (
    select 1
    from public.tenant_members tm
    where tm.tenant_id = p_tenant_id
      and tm.user_id = auth.uid()
      and tm.role = 'tenant_admin'
      and tm.status = 'active'
  );
$$;


ALTER FUNCTION public.is_tenant_admin(p_tenant_id uuid) OWNER TO supabase_admin;

--
-- Name: is_tenant_member(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.is_tenant_member(_tenant_id uuid) RETURNS boolean
CREATE FUNCTION public.is_tenant_admin(p_tenant_id uuid) RETURNS boolean
    LANGUAGE sql STABLE SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
  select exists (
    select 1
    from public.tenant_members tm
    where tm.tenant_id = p_tenant_id
      and tm.user_id = auth.uid()
      and tm.role = 'tenant_admin'
      and tm.status = 'active'
  );
$$;


ALTER FUNCTION public.is_tenant_admin(p_tenant_id uuid) OWNER TO supabase_admin;

--
-- Name: is_tenant_member(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.is_tenant_member(_tenant_id uuid) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select exists (
    select 1
    from public.tenant_members m
    where m.tenant_id = _tenant_id
      and m.user_id = auth.uid()
      and m.status = 'active'
  );
$$;


ALTER FUNCTION public.is_tenant_member(_tenant_id uuid) OWNER TO supabase_admin;

--
-- Name: is_therapist_tenant(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.is_therapist_tenant(_tenant_id uuid) RETURNS boolean
CREATE FUNCTION public.is_tenant_member(_tenant_id uuid) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select exists (
    select 1
    from public.tenant_members m
    where m.tenant_id = _tenant_id
      and m.user_id = auth.uid()
      and m.status = 'active'
  );
$$;


ALTER FUNCTION public.is_tenant_member(_tenant_id uuid) OWNER TO supabase_admin;

--
-- Name: is_therapist_tenant(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.is_therapist_tenant(_tenant_id uuid) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  SELECT EXISTS (
    SELECT 1 FROM public.tenants t
    WHERE t.id = _tenant_id AND t.kind = 'therapist'
  );
$$;


ALTER FUNCTION public.is_therapist_tenant(_tenant_id uuid) OWNER TO supabase_admin;

--
-- Name: jwt_email(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.jwt_email() RETURNS text
CREATE FUNCTION public.is_therapist_tenant(_tenant_id uuid) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  SELECT EXISTS (
    SELECT 1 FROM public.tenants t
    WHERE t.id = _tenant_id AND t.kind = 'therapist'
  );
$$;


ALTER FUNCTION public.is_therapist_tenant(_tenant_id uuid) OWNER TO supabase_admin;

--
-- Name: jwt_email(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.jwt_email() RETURNS text
    LANGUAGE sql STABLE
    AS $$
  select nullif(lower(current_setting('request.jwt.claim.email', true)), '');
$$;


ALTER FUNCTION public.jwt_email() OWNER TO supabase_admin;

--
-- Name: list_financial_records(uuid, integer, integer, text, text, uuid, integer, integer); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.list_financial_records(p_owner_id uuid, p_year integer DEFAULT NULL::integer, p_month integer DEFAULT NULL::integer, p_type text DEFAULT NULL::text, p_status text DEFAULT NULL::text, p_patient_id uuid DEFAULT NULL::uuid, p_limit integer DEFAULT 50, p_offset integer DEFAULT 0) RETURNS SETOF public.financial_records
CREATE FUNCTION public.jwt_email() RETURNS text
    LANGUAGE sql STABLE
    AS $$
  select nullif(lower(current_setting('request.jwt.claim.email', true)), '');
$$;


ALTER FUNCTION public.jwt_email() OWNER TO supabase_admin;

--
-- Name: list_financial_records(uuid, integer, integer, text, text, uuid, integer, integer); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.list_financial_records(p_owner_id uuid, p_year integer DEFAULT NULL::integer, p_month integer DEFAULT NULL::integer, p_type text DEFAULT NULL::text, p_status text DEFAULT NULL::text, p_patient_id uuid DEFAULT NULL::uuid, p_limit integer DEFAULT 50, p_offset integer DEFAULT 0) RETURNS SETOF public.financial_records
CREATE FUNCTION public.my_tenants() RETURNS TABLE(tenant_id uuid, role text, status text, kind text)
    LANGUAGE sql STABLE
    AS $$
  select
    tm.tenant_id,
    tm.role,
    tm.status,
    t.kind
  from public.tenant_members tm
  join public.tenants t on t.id = tm.tenant_id
  where tm.user_id = auth.uid();
$$;


ALTER FUNCTION public.my_tenants() OWNER TO supabase_admin;

--
-- Name: notice_track_click(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.notice_track_click(p_notice_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
begin
  update public.global_notices
  set clicks_count = clicks_count + 1
  where id = p_notice_id;
end;
$$;


ALTER FUNCTION public.notice_track_click(p_notice_id uuid) OWNER TO supabase_admin;

--
-- Name: notice_track_view(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.notice_track_view(p_notice_id uuid) RETURNS void
CREATE FUNCTION public.on_new_user_seed_patient_groups() RETURNS trigger
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
  BEGIN
    PERFORM public.seed_default_patient_groups(NEW.id);
    RETURN NEW;
  END;
  $$;


ALTER FUNCTION public.on_new_user_seed_patient_groups() OWNER TO supabase_admin;

--
-- Name: patients_validate_member_consistency(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.patients_validate_member_consistency() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
DECLARE
  v_tenant_responsible uuid;
  v_tenant_therapist uuid;
BEGIN
  -- responsible_member sempre deve existir e ser do tenant
  SELECT tenant_id INTO v_tenant_responsible
  FROM public.tenant_members
  WHERE id = NEW.responsible_member_id;

  IF v_tenant_responsible IS NULL THEN
    RAISE EXCEPTION 'Responsible member not found';
  END IF;

  IF NEW.tenant_id IS NULL THEN
    RAISE EXCEPTION 'tenant_id is required';
  END IF;

  IF v_tenant_responsible <> NEW.tenant_id THEN
    RAISE EXCEPTION 'Responsible member must belong to the same tenant';
  END IF;

  -- therapist scope: therapist_member_id deve existir e ser do mesmo tenant
  IF NEW.patient_scope = 'therapist' THEN
    IF NEW.therapist_member_id IS NULL THEN
      RAISE EXCEPTION 'therapist_member_id is required when patient_scope=therapist';
    END IF;

    SELECT tenant_id INTO v_tenant_therapist
    FROM public.tenant_members
    WHERE id = NEW.therapist_member_id;

    IF v_tenant_therapist IS NULL THEN
      RAISE EXCEPTION 'Therapist member not found';
    END IF;

    IF v_tenant_therapist <> NEW.tenant_id THEN
      RAISE EXCEPTION 'Therapist member must belong to the same tenant';
    END IF;
  END IF;

  RETURN NEW;
END;
$$;


ALTER FUNCTION public.patients_validate_member_consistency() OWNER TO supabase_admin;

--
-- Name: patients_validate_responsible_member_tenant(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.patients_validate_responsible_member_tenant() RETURNS trigger
CREATE FUNCTION public.prevent_promoting_to_system() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
  if new.is_system = true and old.is_system is distinct from true then
    raise exception 'Não é permitido transformar um grupo comum em grupo do sistema.';
  end if;
  return new;
end;
$$;


ALTER FUNCTION public.prevent_promoting_to_system() OWNER TO supabase_admin;

--
-- Name: prevent_saas_membership(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.prevent_saas_membership() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  IF EXISTS (
    SELECT 1
    FROM public.profiles
    WHERE id = NEW.user_id
    AND role = 'saas_admin'
  ) THEN
    RAISE EXCEPTION 'SaaS admin cannot belong to tenant';
  END IF;

  RETURN NEW;
END;
$$;


ALTER FUNCTION public.prevent_saas_membership() OWNER TO supabase_admin;

--
-- Name: prevent_system_group_changes(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.prevent_system_group_changes() RETURNS trigger
CREATE FUNCTION public.prevent_saas_membership() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
BEGIN
  IF EXISTS (
    SELECT 1
    FROM public.profiles
    WHERE id = NEW.user_id
    AND role = 'saas_admin'
  ) THEN
    RAISE EXCEPTION 'SaaS admin cannot belong to tenant';
  END IF;

  RETURN NEW;
END;
$$;


ALTER FUNCTION public.prevent_saas_membership() OWNER TO supabase_admin;

--
-- Name: prevent_system_group_changes(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.prevent_system_group_changes() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
begin
  -- Se for grupo do sistema, regras rígidas:
  if old.is_system = true then

    -- nunca pode deletar
    if tg_op = 'DELETE' then
      raise exception 'Grupos padrão do sistema não podem ser alterados ou excluídos.';
    end if;

    if tg_op = 'UPDATE' then
      -- permite SOMENTE mudar tenant_id e/ou updated_at
      -- qualquer mudança de conteúdo permanece proibida
      if
        new.nome         is distinct from old.nome or
        new.descricao    is distinct from old.descricao or
        new.cor          is distinct from old.cor or
        new.is_active    is distinct from old.is_active or
        new.is_system    is distinct from old.is_system or
        new.owner_id     is distinct from old.owner_id or
        new.therapist_id is distinct from old.therapist_id or
        new.created_at   is distinct from old.created_at
      then
        raise exception 'Grupos padrão do sistema não podem ser alterados ou excluídos.';
      end if;

      -- chegou aqui: só tenant_id/updated_at mudaram -> ok
      return new;
    end if;

  end if;

  -- não-system: deixa passar
  if tg_op = 'DELETE' then
    return old;
  end if;

  return new;
end;
$$;


ALTER FUNCTION public.prevent_system_group_changes() OWNER TO supabase_admin;

--
-- Name: provision_account_tenant(uuid, text, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.provision_account_tenant(p_user_id uuid, p_kind text, p_name text DEFAULT NULL::text) RETURNS uuid
CREATE FUNCTION public.provision_account_tenant(p_user_id uuid, p_kind text, p_name text DEFAULT NULL::text) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    AS $$
DECLARE
  v_tenant_id    uuid;
  v_account_type text;
  v_name         text;
BEGIN
  IF p_kind NOT IN ('therapist', 'clinic_coworking', 'clinic_reception', 'clinic_full') THEN
    RAISE EXCEPTION 'kind inválido: "%". Use: therapist, clinic_coworking, clinic_reception, clinic_full.', p_kind
      USING ERRCODE = 'P0001';
  END IF;

  v_account_type := CASE WHEN p_kind = 'therapist' THEN 'therapist' ELSE 'clinic' END;

  IF EXISTS (
    SELECT 1
    FROM public.tenant_members tm
    JOIN public.tenants t ON t.id = tm.tenant_id
    WHERE tm.user_id = p_user_id
      AND tm.role = 'tenant_admin'
      AND tm.status = 'active'
      AND t.kind = p_kind
  ) THEN
    RAISE EXCEPTION 'Usuário já possui um tenant do tipo "%".', p_kind
      USING ERRCODE = 'P0001';
  END IF;

  v_name := COALESCE(
    NULLIF(TRIM(p_name), ''),
    (
      SELECT COALESCE(NULLIF(TRIM(pr.full_name), ''), SPLIT_PART(au.email, '@', 1))
      FROM public.profiles pr
      JOIN auth.users au ON au.id = pr.id
      WHERE pr.id = p_user_id
    ),
    'Conta'
  );

  INSERT INTO public.tenants (name, kind, created_at)
  VALUES (v_name, p_kind, now())
  RETURNING id INTO v_tenant_id;

  INSERT INTO public.tenant_members (tenant_id, user_id, role, status, created_at)
  VALUES (v_tenant_id, p_user_id, 'tenant_admin', 'active', now());

  UPDATE public.profiles
  SET account_type = v_account_type
  WHERE id = p_user_id;

  PERFORM public.seed_determined_commitments(v_tenant_id);

  RETURN v_tenant_id;
END;
$$;


ALTER FUNCTION public.provision_account_tenant(p_user_id uuid, p_kind text, p_name text) OWNER TO supabase_admin;

--
-- Name: FUNCTION provision_account_tenant(p_user_id uuid, p_kind text, p_name text); Type: COMMENT; Schema: public; Owner: supabase_admin
--

COMMENT ON FUNCTION public.provision_account_tenant(p_user_id uuid, p_kind text, p_name text) IS 'Cria o tenant do tipo correto e atualiza account_type no profile. Chamar no onboarding após escolha/pagamento de plano therapist ou clinic. p_kind: therapist | clinic_coworking | clinic_reception | clinic_full';


--
-- Name: reactivate_subscription(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.reactivate_subscription(p_subscription_id uuid) RETURNS public.subscriptions
CREATE FUNCTION public.tenant_accept_invite(p_token uuid) RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public', 'auth'
    AS $$
declare
  v_uid uuid;
  v_email text;
  v_invite public.tenant_invites%rowtype;
begin
  -- 1) precisa estar autenticado
  v_uid := auth.uid();
  if v_uid is null then
    raise exception 'not_authenticated' using errcode = 'P0001';
  end if;

  -- 2) pega email real do usuário logado sem depender do JWT claim
  select u.email
    into v_email
  from auth.users u
  where u.id = v_uid;

  if v_email is null or length(trim(v_email)) = 0 then
    raise exception 'missing_user_email' using errcode = 'P0001';
  end if;

  -- 3) carrega o invite e trava linha (evita 2 aceites concorrentes)
  select *
    into v_invite
  from public.tenant_invites i
  where i.token = p_token
  for update;

  if not found then
    raise exception 'invite_not_found' using errcode = 'P0001';
  end if;

  -- 4) validações de estado
  if v_invite.revoked_at is not null then
    raise exception 'invite_revoked' using errcode = 'P0001';
  end if;

  if v_invite.accepted_at is not null then
    raise exception 'invite_already_accepted' using errcode = 'P0001';
  end if;

  if v_invite.expires_at is not null and v_invite.expires_at <= now() then
    raise exception 'invite_expired' using errcode = 'P0001';
  end if;

  -- 5) valida email (case-insensitive)
  if lower(trim(v_invite.email)) <> lower(trim(v_email)) then
    raise exception 'email_mismatch' using errcode = 'P0001';
  end if;

  -- 6) consome o invite
  update public.tenant_invites
  set accepted_at = now(),
      accepted_by = v_uid
  where id = v_invite.id;

  -- 7) cria ou reativa o membership
  insert into public.tenant_members (tenant_id, user_id, role, status, created_at)
  values (v_invite.tenant_id, v_uid, v_invite.role, 'active', now())
  on conflict (tenant_id, user_id)
  do update set
    role = excluded.role,
    status = 'active';

  -- 8) retorno útil pro front (você já tenta ler tenant_id no AcceptInvitePage)
  return jsonb_build_object(
    'ok', true,
    'tenant_id', v_invite.tenant_id,
    'role', v_invite.role
  );
end;
$$;


ALTER FUNCTION public.tenant_accept_invite(p_token uuid) OWNER TO supabase_admin;

--
-- Name: tenant_members; Type: TABLE; Schema: public; Owner: supabase_admin
--

CREATE TABLE public.tenant_members (
    id uuid DEFAULT gen_random_uuid() NOT NULL,
    tenant_id uuid NOT NULL,
    user_id uuid NOT NULL,
    role text NOT NULL,
    status text DEFAULT 'active'::text NOT NULL,
    created_at timestamp with time zone DEFAULT now() NOT NULL
);


ALTER TABLE public.tenant_members OWNER TO supabase_admin;

--
-- Name: tenant_add_member_by_email(uuid, text, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_add_member_by_email(p_tenant_id uuid, p_email text, p_role text DEFAULT 'therapist'::text) RETURNS public.tenant_members
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public', 'auth'
    AS $$
declare
  v_target_uid uuid;
  v_member public.tenant_members%rowtype;
  v_is_admin boolean;
  v_email text;
begin
  if p_tenant_id is null then
    raise exception 'tenant_id é obrigatório';
  end if;

  v_email := lower(trim(coalesce(p_email, '')));
  if v_email = '' then
    raise exception 'email é obrigatório';
  end if;

  -- valida role permitida
  if p_role not in ('tenant_admin','therapist','secretary','patient') then
    raise exception 'role inválida: %', p_role;
  end if;

  -- apenas admin do tenant (role real no banco)
  select exists (
    select 1
    from public.tenant_members tm
    where tm.tenant_id = p_tenant_id
      and tm.user_id = auth.uid()
      and tm.role = 'tenant_admin'
      and coalesce(tm.status,'active') = 'active'
  ) into v_is_admin;

  if not v_is_admin then
    raise exception 'sem permissão: apenas admin da clínica pode adicionar membros';
  end if;

  -- acha usuário pelo e-mail no Supabase Auth
  select u.id
  into v_target_uid
  from auth.users u
  where lower(u.email) = v_email
  limit 1;

  if v_target_uid is null then
    raise exception 'nenhum usuário encontrado com este e-mail';
  end if;

  -- cria ou reativa membro
  insert into public.tenant_members (tenant_id, user_id, role, status)
  values (p_tenant_id, v_target_uid, p_role, 'active')
  on conflict (tenant_id, user_id)
  do update set
    role = excluded.role,
    status = 'active'
  returning * into v_member;

  return v_member;
end;
$$;


ALTER FUNCTION public.tenant_add_member_by_email(p_tenant_id uuid, p_email text, p_role text) OWNER TO supabase_admin;

--
-- Name: tenant_feature_allowed(uuid, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_feature_allowed(p_tenant_id uuid, p_feature_key text) RETURNS boolean
CREATE FUNCTION public.tenant_add_member_by_email(p_tenant_id uuid, p_email text, p_role text DEFAULT 'therapist'::text) RETURNS public.tenant_members
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public', 'auth'
    AS $$
declare
  v_target_uid uuid;
  v_member public.tenant_members%rowtype;
  v_is_admin boolean;
  v_email text;
begin
  if p_tenant_id is null then
    raise exception 'tenant_id é obrigatório';
  end if;

  v_email := lower(trim(coalesce(p_email, '')));
  if v_email = '' then
    raise exception 'email é obrigatório';
  end if;

  -- valida role permitida
  if p_role not in ('tenant_admin','therapist','secretary','patient') then
    raise exception 'role inválida: %', p_role;
  end if;

  -- apenas admin do tenant (role real no banco)
  select exists (
    select 1
    from public.tenant_members tm
    where tm.tenant_id = p_tenant_id
      and tm.user_id = auth.uid()
      and tm.role = 'tenant_admin'
      and coalesce(tm.status,'active') = 'active'
  ) into v_is_admin;

  if not v_is_admin then
    raise exception 'sem permissão: apenas admin da clínica pode adicionar membros';
  end if;

  -- acha usuário pelo e-mail no Supabase Auth
  select u.id
  into v_target_uid
  from auth.users u
  where lower(u.email) = v_email
  limit 1;

  if v_target_uid is null then
    raise exception 'nenhum usuário encontrado com este e-mail';
  end if;

  -- cria ou reativa membro
  insert into public.tenant_members (tenant_id, user_id, role, status)
  values (p_tenant_id, v_target_uid, p_role, 'active')
  on conflict (tenant_id, user_id)
  do update set
    role = excluded.role,
    status = 'active'
  returning * into v_member;

  return v_member;
end;
$$;


ALTER FUNCTION public.tenant_add_member_by_email(p_tenant_id uuid, p_email text, p_role text) OWNER TO supabase_admin;

--
-- Name: tenant_feature_allowed(uuid, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_feature_allowed(p_tenant_id uuid, p_feature_key text) RETURNS boolean
CREATE FUNCTION public.tenant_feature_allowed(p_tenant_id uuid, p_feature_key text) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select exists (
    select 1
    from public.v_tenant_entitlements v
    where v.tenant_id = p_tenant_id
      and v.feature_key = p_feature_key
      and coalesce(v.allowed, false) = true
  );
$$;


ALTER FUNCTION public.tenant_feature_allowed(p_tenant_id uuid, p_feature_key text) OWNER TO supabase_admin;

--
-- Name: tenant_feature_enabled(uuid, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_feature_enabled(p_tenant_id uuid, p_feature_key text) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select coalesce(
    (select tf.enabled
     from public.tenant_features tf
     where tf.tenant_id = p_tenant_id and tf.feature_key = p_feature_key),
    false
  );
$$;


ALTER FUNCTION public.tenant_feature_enabled(p_tenant_id uuid, p_feature_key text) OWNER TO supabase_admin;

--
-- Name: tenant_features_guard_with_plan(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_features_guard_with_plan() RETURNS trigger
CREATE FUNCTION public.tenant_feature_enabled(p_tenant_id uuid, p_feature_key text) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select coalesce(
    (select tf.enabled
     from public.tenant_features tf
     where tf.tenant_id = p_tenant_id and tf.feature_key = p_feature_key),
    false
  );
$$;


ALTER FUNCTION public.tenant_feature_enabled(p_tenant_id uuid, p_feature_key text) OWNER TO supabase_admin;

--
-- Name: tenant_features_guard_with_plan(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_features_guard_with_plan() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
declare
  v_allowed boolean;
begin
  -- só valida quando está habilitando
  if new.enabled is distinct from true then
    return new;
  end if;

  -- permitido pelo plano do tenant?
  select exists (
    select 1
    from public.v_tenant_entitlements_full v
    where v.tenant_id = new.tenant_id
      and v.feature_key = new.feature_key
      and v.allowed = true
  )
  into v_allowed;

  if not v_allowed then
    raise exception 'Feature % não permitida pelo plano atual do tenant %.',
      new.feature_key, new.tenant_id
      using errcode = 'P0001';
  end if;

  return new;
end;
$$;


ALTER FUNCTION public.tenant_features_guard_with_plan() OWNER TO supabase_admin;

--
-- Name: tenant_has_feature(uuid, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_has_feature(_tenant_id uuid, _feature text) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select
    exists (
      select 1
      from public.v_tenant_entitlements e
      where e.tenant_id = _tenant_id
        and e.feature_key = _feature
        and e.allowed = true
    )
    or exists (
      select 1
      from public.tenant_features tf
      where tf.tenant_id = _tenant_id
        and tf.feature_key = _feature
        and tf.enabled = true
    );
$$;


ALTER FUNCTION public.tenant_has_feature(_tenant_id uuid, _feature text) OWNER TO supabase_admin;

--
-- Name: tenant_invite_member_by_email(uuid, text, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_invite_member_by_email(p_tenant_id uuid, p_email text, p_role text) RETURNS uuid
CREATE FUNCTION public.tenant_features_guard_with_plan() RETURNS trigger
    LANGUAGE plpgsql
    AS $$
declare
  v_allowed boolean;
begin
  -- só valida quando está habilitando
  if new.enabled is distinct from true then
    return new;
  end if;

  -- permitido pelo plano do tenant?
  select exists (
    select 1
    from public.v_tenant_entitlements_full v
    where v.tenant_id = new.tenant_id
      and v.feature_key = new.feature_key
      and v.allowed = true
  )
  into v_allowed;

  if not v_allowed then
    raise exception 'Feature % não permitida pelo plano atual do tenant %.',
      new.feature_key, new.tenant_id
      using errcode = 'P0001';
  end if;

  return new;
end;
$$;


ALTER FUNCTION public.tenant_features_guard_with_plan() OWNER TO supabase_admin;

--
-- Name: tenant_has_feature(uuid, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_has_feature(_tenant_id uuid, _feature text) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select
    exists (
      select 1
      from public.v_tenant_entitlements e
      where e.tenant_id = _tenant_id
        and e.feature_key = _feature
        and e.allowed = true
    )
    or exists (
      select 1
      from public.tenant_features tf
      where tf.tenant_id = _tenant_id
        and tf.feature_key = _feature
        and tf.enabled = true
    );
$$;


ALTER FUNCTION public.tenant_has_feature(_tenant_id uuid, _feature text) OWNER TO supabase_admin;

--
-- Name: tenant_invite_member_by_email(uuid, text, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_invite_member_by_email(p_tenant_id uuid, p_email text, p_role text) RETURNS uuid
CREATE FUNCTION public.tenant_has_feature(_tenant_id uuid, _feature text) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select
    exists (
      select 1
      from public.v_tenant_entitlements e
      where e.tenant_id = _tenant_id
        and e.feature_key = _feature
        and e.allowed = true
    )
    or exists (
      select 1
      from public.tenant_features tf
      where tf.tenant_id = _tenant_id
        and tf.feature_key = _feature
        and tf.enabled = true
    );
$$;


ALTER FUNCTION public.tenant_has_feature(_tenant_id uuid, _feature text) OWNER TO supabase_admin;

--
-- Name: tenant_invite_member_by_email(uuid, text, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_invite_member_by_email(p_tenant_id uuid, p_email text, p_role text) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public', 'auth'
    AS $$
declare
  v_email text;
  v_my_email text;
  v_token uuid;
  v_updated int;
begin
  -- validações básicas
  if p_tenant_id is null then
    raise exception 'tenant_id inválido' using errcode = 'P0001';
  end if;

  v_email := lower(trim(coalesce(p_email, '')));
  if v_email = '' then
    raise exception 'Informe um email' using errcode = 'P0001';
  end if;

  -- role permitido (ajuste se quiser)
  if p_role is null or p_role not in ('therapist', 'secretary') then
    raise exception 'Role inválido (use therapist/secretary)' using errcode = 'P0001';
  end if;

  -- ✅ bloqueio: auto-convite
  v_my_email := public.get_my_email();
  if v_my_email is not null and v_email = v_my_email then
    raise exception 'Você não pode convidar o seu próprio email.' using errcode = 'P0001';
  end if;

  -- ✅ bloqueio: já é membro ativo do tenant
  if exists (
    select 1
    from tenant_members tm
    join auth.users au on au.id = tm.user_id
    where tm.tenant_id = p_tenant_id
      and tm.status = 'active'
      and lower(au.email) = v_email
  ) then
    raise exception 'Este email já está vinculado a esta clínica.' using errcode = 'P0001';
  end if;

  -- ✅ permissão: só admin do tenant pode convidar
  if not exists (
    select 1
    from tenant_members me
    where me.tenant_id = p_tenant_id
      and me.user_id = auth.uid()
      and me.status = 'active'
      and me.role in ('tenant_admin','clinic_admin')
  ) then
    raise exception 'Sem permissão para convidar membros.' using errcode = 'P0001';
  end if;

  -- Gera token (reenvio simples / regeneração)
  v_token := gen_random_uuid();

  -- 1) tenta "regerar" um convite pendente existente (mesmo email)
  update tenant_invites
     set token = v_token,
         role = p_role,
         created_at = now(),
         expires_at = now() + interval '7 days',
         accepted_at = null,
         revoked_at = null
   where tenant_id = p_tenant_id
     and lower(email) = v_email
     and accepted_at is null
     and revoked_at is null;

  get diagnostics v_updated = row_count;

  -- 2) se não atualizou nada, cria convite novo
  if v_updated = 0 then
    insert into tenant_invites (tenant_id, email, role, token, created_at, expires_at)
    values (p_tenant_id, v_email, p_role, v_token, now(), now() + interval '7 days');
  end if;

  return v_token;
end;
$$;


ALTER FUNCTION public.tenant_invite_member_by_email(p_tenant_id uuid, p_email text, p_role text) OWNER TO supabase_admin;

--
-- Name: tenant_reactivate_member(uuid, uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_reactivate_member(p_tenant_id uuid, p_member_user_id uuid) RETURNS void
CREATE FUNCTION public.tenant_invite_member_by_email(p_tenant_id uuid, p_email text, p_role text) RETURNS uuid
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public', 'auth'
    AS $$
declare
  v_email text;
  v_my_email text;
  v_token uuid;
  v_updated int;
begin
  -- validações básicas
  if p_tenant_id is null then
    raise exception 'tenant_id inválido' using errcode = 'P0001';
  end if;

  v_email := lower(trim(coalesce(p_email, '')));
  if v_email = '' then
    raise exception 'Informe um email' using errcode = 'P0001';
  end if;

  -- role permitido (ajuste se quiser)
  if p_role is null or p_role not in ('therapist', 'secretary') then
    raise exception 'Role inválido (use therapist/secretary)' using errcode = 'P0001';
  end if;

  -- ✅ bloqueio: auto-convite
  v_my_email := public.get_my_email();
  if v_my_email is not null and v_email = v_my_email then
    raise exception 'Você não pode convidar o seu próprio email.' using errcode = 'P0001';
  end if;

  -- ✅ bloqueio: já é membro ativo do tenant
  if exists (
    select 1
    from tenant_members tm
    join auth.users au on au.id = tm.user_id
    where tm.tenant_id = p_tenant_id
      and tm.status = 'active'
      and lower(au.email) = v_email
  ) then
    raise exception 'Este email já está vinculado a esta clínica.' using errcode = 'P0001';
  end if;

  -- ✅ permissão: só admin do tenant pode convidar
  if not exists (
    select 1
    from tenant_members me
    where me.tenant_id = p_tenant_id
      and me.user_id = auth.uid()
      and me.status = 'active'
      and me.role in ('tenant_admin','clinic_admin')
  ) then
    raise exception 'Sem permissão para convidar membros.' using errcode = 'P0001';
  end if;

  -- Gera token (reenvio simples / regeneração)
  v_token := gen_random_uuid();

  -- 1) tenta "regerar" um convite pendente existente (mesmo email)
  update tenant_invites
     set token = v_token,
         role = p_role,
         created_at = now(),
         expires_at = now() + interval '7 days',
         accepted_at = null,
         revoked_at = null
   where tenant_id = p_tenant_id
     and lower(email) = v_email
     and accepted_at is null
     and revoked_at is null;

  get diagnostics v_updated = row_count;

  -- 2) se não atualizou nada, cria convite novo
  if v_updated = 0 then
    insert into tenant_invites (tenant_id, email, role, token, created_at, expires_at)
    values (p_tenant_id, v_email, p_role, v_token, now(), now() + interval '7 days');
  end if;

  return v_token;
end;
$$;


ALTER FUNCTION public.tenant_invite_member_by_email(p_tenant_id uuid, p_email text, p_role text) OWNER TO supabase_admin;

--
-- Name: tenant_reactivate_member(uuid, uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_reactivate_member(p_tenant_id uuid, p_member_user_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  update public.tenant_members
     set status = 'active'
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


ALTER FUNCTION public.tenant_reactivate_member(p_tenant_id uuid, p_member_user_id uuid) OWNER TO supabase_admin;

--
-- Name: tenant_remove_member(uuid, uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_remove_member(p_tenant_id uuid, p_member_user_id uuid) RETURNS void
CREATE FUNCTION public.tenant_reactivate_member(p_tenant_id uuid, p_member_user_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  update public.tenant_members
     set status = 'active'
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


ALTER FUNCTION public.tenant_reactivate_member(p_tenant_id uuid, p_member_user_id uuid) OWNER TO supabase_admin;

--
-- Name: tenant_remove_member(uuid, uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_remove_member(p_tenant_id uuid, p_member_user_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
declare
  v_role text;
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  if p_member_user_id = auth.uid() then
    raise exception 'cannot_remove_self';
  end if;

  -- pega role atual do membro (se não existir, erro)
  select role into v_role
  from public.tenant_members
  where tenant_id = p_tenant_id
    and user_id = p_member_user_id;

  if v_role is null then
    raise exception 'membership_not_found';
  end if;

  -- trava: se for therapist, não pode remover com eventos futuros
  if v_role = 'therapist' then
    if exists (
      select 1
      from public.agenda_eventos e
      where e.owner_id = p_tenant_id
        and e.terapeuta_id = p_member_user_id
        and e.inicio_em >= now()
        and e.status::text not in ('cancelado','cancelled','canceled')
      limit 1
    ) then
      raise exception 'cannot_remove_therapist_with_future_events';
    end if;
  end if;

  update public.tenant_members
     set status = 'inactive'
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


ALTER FUNCTION public.tenant_remove_member(p_tenant_id uuid, p_member_user_id uuid) OWNER TO supabase_admin;

--
-- Name: tenant_remove_member_soft(uuid, uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_remove_member_soft(p_tenant_id uuid, p_member_user_id uuid) RETURNS void
CREATE FUNCTION public.tenant_remove_member(p_tenant_id uuid, p_member_user_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
declare
  v_role text;
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  if p_member_user_id = auth.uid() then
    raise exception 'cannot_remove_self';
  end if;

  -- pega role atual do membro (se não existir, erro)
  select role into v_role
  from public.tenant_members
  where tenant_id = p_tenant_id
    and user_id = p_member_user_id;

  if v_role is null then
    raise exception 'membership_not_found';
  end if;

  -- trava: se for therapist, não pode remover com eventos futuros
  if v_role = 'therapist' then
    if exists (
      select 1
      from public.agenda_eventos e
      where e.owner_id = p_tenant_id
        and e.terapeuta_id = p_member_user_id
        and e.inicio_em >= now()
        and e.status::text not in ('cancelado','cancelled','canceled')
      limit 1
    ) then
      raise exception 'cannot_remove_therapist_with_future_events';
    end if;
  end if;

  update public.tenant_members
     set status = 'inactive'
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


ALTER FUNCTION public.tenant_remove_member(p_tenant_id uuid, p_member_user_id uuid) OWNER TO supabase_admin;

--
-- Name: tenant_remove_member_soft(uuid, uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_remove_member_soft(p_tenant_id uuid, p_member_user_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  if p_member_user_id = auth.uid() then
    raise exception 'cannot_remove_self';
  end if;

  update public.tenant_members
     set status = 'inactive'
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


ALTER FUNCTION public.tenant_remove_member_soft(p_tenant_id uuid, p_member_user_id uuid) OWNER TO supabase_admin;

--
-- Name: tenant_revoke_invite(uuid, text, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_revoke_invite(p_tenant_id uuid, p_email text, p_role text) RETURNS void
CREATE FUNCTION public.tenant_remove_member_soft(p_tenant_id uuid, p_member_user_id uuid) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  if p_member_user_id = auth.uid() then
    raise exception 'cannot_remove_self';
  end if;

  update public.tenant_members
     set status = 'inactive'
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


ALTER FUNCTION public.tenant_remove_member_soft(p_tenant_id uuid, p_member_user_id uuid) OWNER TO supabase_admin;

--
-- Name: tenant_revoke_invite(uuid, text, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_revoke_invite(p_tenant_id uuid, p_email text, p_role text) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
declare
  v_email text;
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  v_email := lower(trim(p_email));

  update public.tenant_invites
     set revoked_at = now(),
         revoked_by = auth.uid()
   where tenant_id = p_tenant_id
     and lower(email) = v_email
     and role = p_role
     and accepted_at is null
     and revoked_at is null;

  if not found then
    raise exception 'invite_not_found';
  end if;
end;
$$;


ALTER FUNCTION public.tenant_revoke_invite(p_tenant_id uuid, p_email text, p_role text) OWNER TO supabase_admin;

--
-- Name: tenant_set_member_status(uuid, uuid, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_set_member_status(p_tenant_id uuid, p_member_user_id uuid, p_new_status text) RETURNS void
CREATE FUNCTION public.tenant_revoke_invite(p_tenant_id uuid, p_email text, p_role text) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
declare
  v_email text;
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  v_email := lower(trim(p_email));

  update public.tenant_invites
     set revoked_at = now(),
         revoked_by = auth.uid()
   where tenant_id = p_tenant_id
     and lower(email) = v_email
     and role = p_role
     and accepted_at is null
     and revoked_at is null;

  if not found then
    raise exception 'invite_not_found';
  end if;
end;
$$;


ALTER FUNCTION public.tenant_revoke_invite(p_tenant_id uuid, p_email text, p_role text) OWNER TO supabase_admin;

--
-- Name: tenant_set_member_status(uuid, uuid, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_set_member_status(p_tenant_id uuid, p_member_user_id uuid, p_new_status text) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  -- valida status (adapte aos seus valores reais)
  if p_new_status not in ('active','inactive','suspended','invited') then
    raise exception 'invalid_status: %', p_new_status;
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  -- evita desativar a si mesmo (opcional)
  if p_member_user_id = auth.uid() and p_new_status <> 'active' then
    raise exception 'cannot_disable_self';
  end if;

  update public.tenant_members
     set status = p_new_status
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


ALTER FUNCTION public.tenant_set_member_status(p_tenant_id uuid, p_member_user_id uuid, p_new_status text) OWNER TO supabase_admin;

--
-- Name: tenant_update_member_role(uuid, uuid, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_update_member_role(p_tenant_id uuid, p_member_user_id uuid, p_new_role text) RETURNS void
CREATE FUNCTION public.tenant_set_member_status(p_tenant_id uuid, p_member_user_id uuid, p_new_status text) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
begin
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  -- valida status (adapte aos seus valores reais)
  if p_new_status not in ('active','inactive','suspended','invited') then
    raise exception 'invalid_status: %', p_new_status;
  end if;

  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  -- evita desativar a si mesmo (opcional)
  if p_member_user_id = auth.uid() and p_new_status <> 'active' then
    raise exception 'cannot_disable_self';
  end if;

  update public.tenant_members
     set status = p_new_status
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


ALTER FUNCTION public.tenant_set_member_status(p_tenant_id uuid, p_member_user_id uuid, p_new_status text) OWNER TO supabase_admin;

--
-- Name: tenant_update_member_role(uuid, uuid, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.tenant_update_member_role(p_tenant_id uuid, p_member_user_id uuid, p_new_role text) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
begin
  -- exige auth
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  -- valida role
  if p_new_role not in ('tenant_admin','therapist','secretary','patient') then
    raise exception 'invalid_role: %', p_new_role;
  end if;

  -- somente tenant_admin ativo pode alterar role
  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  -- evita o admin remover o próprio admin sem querer (opcional mas recomendado)
  if p_member_user_id = auth.uid() and p_new_role <> 'tenant_admin' then
    raise exception 'cannot_demote_self';
  end if;

  update public.tenant_members
     set role = p_new_role
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


ALTER FUNCTION public.tenant_update_member_role(p_tenant_id uuid, p_member_user_id uuid, p_new_role text) OWNER TO supabase_admin;

--
-- Name: toggle_plan(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.toggle_plan(owner uuid) RETURNS void
CREATE FUNCTION public.tenant_update_member_role(p_tenant_id uuid, p_member_user_id uuid, p_new_role text) RETURNS void
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    SET row_security TO 'off'
    AS $$
begin
  -- exige auth
  if auth.uid() is null then
    raise exception 'not_authenticated';
  end if;

  -- valida role
  if p_new_role not in ('tenant_admin','therapist','secretary','patient') then
    raise exception 'invalid_role: %', p_new_role;
  end if;

  -- somente tenant_admin ativo pode alterar role
  if not public.is_tenant_admin(p_tenant_id) then
    raise exception 'not_allowed';
  end if;

  -- evita o admin remover o próprio admin sem querer (opcional mas recomendado)
  if p_member_user_id = auth.uid() and p_new_role <> 'tenant_admin' then
    raise exception 'cannot_demote_self';
  end if;

  update public.tenant_members
     set role = p_new_role
   where tenant_id = p_tenant_id
     and user_id = p_member_user_id;

  if not found then
    raise exception 'membership_not_found';
  end if;
end;
$$;


ALTER FUNCTION public.tenant_update_member_role(p_tenant_id uuid, p_member_user_id uuid, p_new_role text) OWNER TO supabase_admin;

--
-- Name: toggle_plan(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.toggle_plan(owner uuid) RETURNS void
CREATE FUNCTION public.user_has_feature(_user_id uuid, _feature text) RETURNS boolean
    LANGUAGE sql STABLE
    AS $$
  select exists (
    select 1
    from public.v_user_entitlements e
    where e.user_id = _user_id
      and e.feature_key = _feature
      and e.allowed = true
  );
$$;


ALTER FUNCTION public.user_has_feature(_user_id uuid, _feature text) OWNER TO supabase_admin;

--
-- Name: validate_support_session(text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.validate_support_session(p_token text) RETURNS json
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
DECLARE
  v_session support_sessions;
BEGIN
  IF p_token IS NULL OR length(trim(p_token)) < 32 THEN
    RETURN json_build_object('valid', false, 'tenant_id', null);
  END IF;

  SELECT * INTO v_session
    FROM public.support_sessions
   WHERE token = p_token
     AND expires_at > now()
   LIMIT 1;

  IF NOT FOUND THEN
    RETURN json_build_object('valid', false, 'tenant_id', null);
  END IF;

  RETURN json_build_object(
    'valid',     true,
    'tenant_id', v_session.tenant_id
  );
END;
$$;


ALTER FUNCTION public.validate_support_session(p_token text) OWNER TO supabase_admin;

--
-- Name: whoami(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.whoami() RETURNS TABLE(uid uuid, role text)
    LANGUAGE sql STABLE
    AS $$
  select auth.uid() as uid, auth.role() as role;
$$;


ALTER FUNCTION public.whoami() OWNER TO supabase_admin;

--
-- Name: apply_rls(jsonb, integer); Type: FUNCTION; Schema: realtime; Owner: supabase_admin
--

CREATE FUNCTION realtime.apply_rls(wal jsonb, max_record_bytes integer DEFAULT (1024 * 1024)) RETURNS SETOF realtime.wal_rls
CREATE FUNCTION public.current_member_id(p_tenant_id uuid) RETURNS uuid
    LANGUAGE sql STABLE
    AS $$
  select tm.id
  from public.tenant_members tm
  where tm.tenant_id = p_tenant_id
    and tm.user_id = auth.uid()
  limit 1
$$;


ALTER FUNCTION public.current_member_id(p_tenant_id uuid) OWNER TO supabase_admin;

--
-- Name: current_member_role(uuid); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.current_member_role(p_tenant_id uuid) RETURNS text
    LANGUAGE sql STABLE
    AS $$
  select tm.role
  from public.tenant_members tm
  where tm.tenant_id = p_tenant_id
    and tm.user_id = auth.uid()
  limit 1
$$;


ALTER FUNCTION public.current_member_role(p_tenant_id uuid) OWNER TO supabase_admin;

--
-- Name: debit_addon_credit(uuid, text, uuid, text); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.debit_addon_credit(p_tenant_id uuid, p_addon_type text, p_queue_id uuid DEFAULT NULL::uuid, p_description text DEFAULT 'Consumo'::text) RETURNS jsonb
CREATE FUNCTION public.dev_list_auth_users(p_limit integer DEFAULT 50) RETURNS TABLE(id uuid, email text, created_at timestamp with time zone)
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public', 'auth'
    AS $$
begin
  -- só saas_admin pode ver
  if not exists (
    select 1
    from public.profiles p
    where p.id = auth.uid()
      and p.role = 'saas_admin'
  ) then
    return;
  end if;

  return query
  select
    u.id,
    u.email,
    u.created_at
  from auth.users u
  order by u.created_at desc
  limit greatest(1, least(coalesce(p_limit, 50), 500));
end;
$$;


ALTER FUNCTION public.dev_list_auth_users(p_limit integer) OWNER TO supabase_admin;

--
-- Name: dev_list_custom_users(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.dev_list_custom_users() RETURNS TABLE(user_id uuid, email text, created_at timestamp with time zone, global_role text, tenant_role text, tenant_id uuid, password_dev text, kind text)
    LANGUAGE sql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
  with base as (
    select
      u.id as user_id,
      lower(u.email) as email,
      u.created_at
    from auth.users u
    where lower(u.email) not in (
      'clinic@agenciapsi.com.br',
      'therapist@agenciapsi.com.br',
      'patient@agenciapsi.com.br',
      'saas@agenciapsi.com.br'
    )
  ),
  prof as (
    select p.id, p.role as global_role
    from public.profiles p
  ),
  last_membership as (
    select distinct on (tm.user_id)
      tm.user_id,
      tm.tenant_id,
      tm.role as tenant_role,
      tm.created_at
    from public.tenant_members tm
    where tm.status = 'active'
    order by tm.user_id, tm.created_at desc
  )
  select
    b.user_id,
    b.email,
    b.created_at,
    pr.global_role,
    lm.tenant_role,
    lm.tenant_id,
    dc.password_dev,
    dc.kind
  from base b
  left join prof pr on pr.id = b.user_id
  left join last_membership lm on lm.user_id = b.user_id
  left join public.dev_user_credentials dc on lower(dc.email) = b.email
  order by b.created_at desc;
$$;


ALTER FUNCTION public.dev_list_custom_users() OWNER TO supabase_admin;

--
-- Name: dev_list_intent_leads(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.dev_list_intent_leads() RETURNS TABLE(email text, last_intent_at timestamp with time zone, plan_key text, billing_interval text, status text, tenant_id uuid)
    LANGUAGE sql SECURITY DEFINER
    SET search_path TO 'public'
    AS $$
  select
    lower(si.email) as email,
    max(si.created_at) as last_intent_at,
    (array_agg(si.plan_key order by si.created_at desc))[1] as plan_key,
    (array_agg(si.interval order by si.created_at desc))[1] as billing_interval,
    (array_agg(si.status order by si.created_at desc))[1] as status,
    (array_agg(si.tenant_id order by si.created_at desc))[1] as tenant_id
  from public.subscription_intents si
  where si.email is not null
    and not exists (
      select 1
      from auth.users au
      where lower(au.email) = lower(si.email)
    )
  group by lower(si.email)
  order by max(si.created_at) desc;
$$;


ALTER FUNCTION public.dev_list_intent_leads() OWNER TO supabase_admin;

--
-- Name: dev_public_debug_snapshot(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.dev_public_debug_snapshot() RETURNS TABLE(users_total integer, tenants_total integer, intents_new_total integer, latest_intents jsonb)
    LANGUAGE plpgsql SECURITY DEFINER
    SET search_path TO 'public'
    AS $_$
declare
  v_latest jsonb;
begin
  select jsonb_agg(
    jsonb_build_object(
      'created_at', si.created_at,
      'email_masked',
        regexp_replace(lower(si.email), '(^.).*(@.*$)', '\1***\2'),
      'plan_key', si.plan_key,
      'status', si.status
    )
    order by si.created_at desc
  )
  into v_latest
  from (
    select si.*
    from public.subscription_intents si
    where si.email is not null
    order by si.created_at desc
    limit 5
  ) si;

  return query
  select
    (select count(*)::int from auth.users) as users_total,
    (select count(*)::int from public.tenants) as tenants_total,
    (select count(*)::int from public.subscription_intents where status = 'new') as intents_new_total,
    coalesce(v_latest, '[]'::jsonb) as latest_intents;
end;
$_$;


ALTER FUNCTION public.dev_public_debug_snapshot() OWNER TO supabase_admin;

--
-- Name: ensure_personal_tenant(); Type: FUNCTION; Schema: public; Owner: supabase_admin
--

CREATE FUNCTION public.ensure_personal_tenant() RETURNS uuid
